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.utils import Utils
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.data import Data
class AimedbProtocol(Protocol):
AIMEDB_RESPONSE_CODES = {
"felica_lookup": 0x03,
"lookup": 0x06,
"log": 0x0a,
"campaign": 0x0c,
"touch": 0x0e,
"log": 0x0A,
"campaign": 0x0C,
"touch": 0x0E,
"lookup2": 0x10,
"felica_lookup2": 0x12,
"log2": 0x14,
"hello": 0x65
"hello": 0x65,
}
request_list: Dict[int, Any] = {}
@ -30,14 +31,14 @@ class AimedbProtocol(Protocol):
if core_cfg.aimedb.key == "":
self.logger.error("!!!KEY NOT SET!!!")
exit(1)
self.request_list[0x01] = self.handle_felica_lookup
self.request_list[0x04] = self.handle_lookup
self.request_list[0x05] = self.handle_register
self.request_list[0x09] = self.handle_log
self.request_list[0x0b] = self.handle_campaign
self.request_list[0x0d] = self.handle_touch
self.request_list[0x0f] = self.handle_lookup2
self.request_list[0x09] = self.handle_log
self.request_list[0x0B] = self.handle_campaign
self.request_list[0x0D] = self.handle_touch
self.request_list[0x0F] = self.handle_lookup2
self.request_list[0x11] = self.handle_felica_lookup2
self.request_list[0x13] = self.handle_log2
self.request_list[0x64] = self.handle_hello
@ -53,8 +54,10 @@ class AimedbProtocol(Protocol):
self.logger.debug(f"{self.transport.getPeer().host} Connected")
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:
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()}")
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")
return None
@ -90,30 +93,46 @@ class AimedbProtocol(Protocol):
except ValueError as e:
self.logger.error(f"Failed to encrypt {resp.hex()} because {e}")
return None
def handle_campaign(self, data: bytes) -> bytes:
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)
def handle_hello(self, data: bytes) -> bytes:
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)
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)
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))
if user_id is None: ret += struct.pack("<iH", -1, 0)
else: ret += struct.pack("<l", user_id)
if user_id is None:
ret += struct.pack("<iH", -1, 0)
else:
ret += struct.pack("<l", user_id)
return self.append_padding(ret)
def handle_lookup2(self, data: bytes) -> bytes:
@ -125,66 +144,98 @@ class AimedbProtocol(Protocol):
return bytes(ret)
def handle_felica_lookup(self, data: bytes) -> bytes:
idm = data[0x20: 0x28].hex()
pmm = data[0x28: 0x30].hex()
idm = data[0x20:0x28].hex()
pmm = data[0x28:0x30].hex()
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.fromhex(access_code)
return self.append_padding(ret)
def handle_felica_lookup2(self, data: bytes) -> bytes:
idm = data[0x30: 0x38].hex()
pmm = data[0x38: 0x40].hex()
idm = data[0x30:0x38].hex()
pmm = data[0x38:0x40].hex()
access_code = self.data.card.to_access_code(idm)
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 += 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 += struct.pack("<l", 1)
return self.append_padding(ret)
def handle_touch(self, data: bytes) -> bytes:
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 += struct.pack("<3H", 0x6f, 0, 1)
ret += struct.pack("<3H", 0x6F, 0, 1)
return self.append_padding(ret)
def handle_register(self, data: bytes) -> bytes:
luid = data[0x20: 0x2a].hex()
def handle_register(self, data: bytes) -> bytes:
luid = data[0x20:0x2A].hex()
if self.config.server.allow_user_registration:
user_id = self.data.user.create_user()
if user_id is None:
if user_id is None:
user_id = -1
self.logger.error("Failed to register user!")
else:
card_id = self.data.card.create_card(user_id, luid)
if card_id is None:
if card_id is None:
user_id = -1
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:
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
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 += struct.pack("<l", user_id)
@ -193,42 +244,54 @@ class AimedbProtocol(Protocol):
def handle_log(self, data: bytes) -> bytes:
# TODO: Save aimedb logs
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)
def handle_log2(self, data: bytes) -> bytes:
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 += struct.pack("H", 1)
return self.append_padding(ret)
class AimedbFactory(Factory):
protocol = AimedbProtocol
def __init__(self, cfg: CoreConfig) -> None:
self.config = cfg
log_fmt_str = "[%(asctime)s] Aimedb | %(levelname)s | %(message)s"
log_fmt = logging.Formatter(log_fmt_str)
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)
consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt)
self.logger.addHandler(fileHandler)
self.logger.addHandler(consoleHandler)
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 == "":
self.logger.error("Please set 'key' field in your config file.")
exit(1)
self.logger.info(f"Ready on port {self.config.aimedb.port}")
def buildProtocol(self, addr):
return AimedbProtocol(self.config)

View File

@ -16,8 +16,9 @@ from core.data import Data
from core.utils import Utils
from core.const import *
class AllnetServlet:
def __init__(self, core_cfg: CoreConfig, cfg_folder: str):
def __init__(self, core_cfg: CoreConfig, cfg_folder: str):
super().__init__()
self.config = core_cfg
self.config_folder = cfg_folder
@ -27,35 +28,45 @@ class AllnetServlet:
self.logger = logging.getLogger("allnet")
if not hasattr(self.logger, "initialized"):
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)
consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt)
self.logger.addHandler(fileHandler)
self.logger.addHandler(consoleHandler)
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
plugins = Utils.get_all_titles()
if len(plugins) == 0:
self.logger.error("No games detected!")
for _, mod in plugins.items():
if hasattr(mod.index, "get_allnet_info"):
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:
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):
request_ip = request.getClientAddress().host
@ -67,14 +78,22 @@ class AllnetServlet:
req = AllnetPowerOnRequest(req_dict[0])
# 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:
raise AllnetRequestException(f"Bad auth request params from {request_ip} - {vars(req)}")
if (
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:
if e.message != "":
self.logger.error(e)
return b""
if req.format_ver == 3:
resp = AllnetPowerOnResponse3(req.token)
else:
@ -83,26 +102,32 @@ class AllnetServlet:
self.logger.debug(f"Allnet request: {vars(req)}")
if req.game_id not in self.uri_registry:
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)
resp.stat = 0
return self.dict_to_http_form_string([vars(resp)])
resp.uri, resp.host = self.uri_registry[req.game_id]
machine = self.data.arcade.get_machine(req.serial)
if machine is None and not self.config.server.allow_unregistered_serials:
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)
resp.stat = 0
return self.dict_to_http_form_string([vars(resp)])
if machine is not None:
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:
country = AllnetCountryCode.JAPAN.value
@ -111,16 +136,30 @@ class AllnetServlet:
resp.allnet_id = machine["id"]
resp.name = arcade["name"] if arcade["name"] 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.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.region0 = (
arcade["region_id"]
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.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(".", "")
resp.uri = resp.uri.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}"
self.data.base.log_event("allnet", "ALLNET_AUTH_SUCCESS", logging.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
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:
if e.message != "":
self.logger.error(e)
@ -149,8 +190,8 @@ class AllnetServlet:
resp = AllnetDownloadOrderResponse()
if not self.config.allnet.allow_online_updates:
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)])
def handle_billing_request(self, request: Request, _: Dict):
@ -159,10 +200,10 @@ class AllnetServlet:
if req_dict is None:
self.logger.error(f"Failed to parse request {request.content.getvalue()}")
return b""
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)
digest = SHA.new()
@ -178,30 +219,34 @@ class AllnetServlet:
machine = self.data.arcade.get_machine(kc_serial)
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}."
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)
resp = BillingResponse("", "", "", "")
resp.result = "1"
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}"
)
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:
kc_playlimit += 1024
kc_nearfull += 1024
playlimit = kc_playlimit
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()
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()
# TODO: playhistory
@ -222,16 +267,16 @@ class AllnetServlet:
def kvp_to_dict(self, kvp: List[str]) -> List[Dict[str, Any]]:
ret: List[Dict[str, Any]] = []
for x in kvp:
items = x.split('&')
items = x.split("&")
tmp = {}
for item in items:
kvp = item.split('=')
kvp = item.split("=")
if len(kvp) == 2:
tmp[kvp[0]] = kvp[1]
ret.append(tmp)
return ret
def billing_req_to_dict(self, data: bytes):
@ -241,8 +286,8 @@ class AllnetServlet:
try:
decomp = zlib.decompressobj(-zlib.MAX_WBITS)
unzipped = decomp.decompress(data)
sections = unzipped.decode('ascii').split('\r\n')
sections = unzipped.decode("ascii").split("\r\n")
return self.kvp_to_dict(sections)
except Exception as e:
@ -252,33 +297,38 @@ class AllnetServlet:
def allnet_req_to_dict(self, data: str) -> Optional[List[Dict[str, Any]]]:
"""
Parses an allnet request string into a python dictionary
"""
"""
try:
zipped = base64.b64decode(data)
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)
except Exception as e:
self.logger.error(f"allnet_req_to_dict: {e} while parsing {data}")
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
"""
try:
urlencode = ""
for item in data:
for k,v in item.items():
for k, v in item.items():
urlencode += f"{k}={v}&"
if crlf:
urlencode = urlencode[:-1] + "\r\n"
else:
urlencode = urlencode[:-1] + "\n"
if not trailing_newline:
if crlf:
urlencode = urlencode[:-2]
@ -286,23 +336,24 @@ class AllnetServlet:
urlencode = urlencode[:-1]
return urlencode
except Exception as e:
self.logger.error(f"dict_to_http_form_string: {e} while parsing {data}")
return None
class AllnetPowerOnRequest():
class AllnetPowerOnRequest:
def __init__(self, req: Dict) -> None:
if req is None:
raise AllnetRequestException("Request processing failed")
self.game_id: str = req["game_id"] if "game_id" in req else ""
self.ver: str = req["ver"] if "ver" in req else ""
self.serial: str = req["serial"] if "serial" in req else ""
self.ip: str = req["ip"] if "ip" in req else ""
self.serial: str = req["serial"] if "serial" 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.boot_ver: str = req["boot_ver"] if "boot_ver" in req else ""
self.encode: str = req["encode"] if "encode" in req else ""
try:
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
@ -310,7 +361,8 @@ class AllnetPowerOnRequest():
except ValueError as e:
raise AllnetRequestException(f"Failed to parse int: {e}")
class AllnetPowerOnResponse3():
class AllnetPowerOnResponse3:
def __init__(self, token) -> None:
self.stat = 1
self.uri = ""
@ -326,12 +378,15 @@ class AllnetPowerOnResponse3():
self.country = "JPN"
self.allnet_id = "123"
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.res_ver = "3"
self.token = str(token)
class AllnetPowerOnResponse2():
class AllnetPowerOnResponse2:
def __init__(self) -> None:
self.stat = 1
self.uri = ""
@ -355,23 +410,31 @@ class AllnetPowerOnResponse2():
self.timezone = "+0900"
self.res_class = "PowerOnResponseV2"
class AllnetDownloadOrderRequest():
class AllnetDownloadOrderRequest:
def __init__(self, req: Dict) -> None:
self.game_id = req["game_id"] if "game_id" in req else ""
self.ver = req["ver"] if "ver" in req else ""
self.serial = req["serial"] if "serial" 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:
self.stat = stat
self.serial = serial
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.waitime = "100"
self.linelimit = "1"
@ -383,10 +446,11 @@ class BillingResponse():
self.nearfullsig = nearfull_sig
self.fixlogincnt = "0"
self.fixinterval = "5"
self.playhistory = playhistory
self.playhistory = playhistory
# playhistory -> YYYYMM/C:...
# YYYY -> 4 digit year, MM -> 2 digit month, C -> Playcount during that period
class AllnetRequestException(Exception):
def __init__(self, message="") -> None:
self.message = message

View File

@ -1,33 +1,47 @@
import logging, os
from typing import Any
class ServerConfig:
def __init__(self, parent_config: "CoreConfig") -> None:
self.__config = parent_config
@property
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
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
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
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
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
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:
def __init__(self, parent_config: "CoreConfig") -> None:
@ -35,15 +49,24 @@ class TitleConfig:
@property
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
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
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:
def __init__(self, parent_config: "CoreConfig") -> None:
@ -51,43 +74,70 @@ class DatabaseConfig:
@property
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
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
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
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
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
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
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
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
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
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:
def __init__(self, parent_config: "CoreConfig") -> None:
@ -95,15 +145,24 @@ class FrontendConfig:
@property
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
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
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:
def __init__(self, parent_config: "CoreConfig") -> None:
@ -111,15 +170,24 @@ class AllnetConfig:
@property
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
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
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:
def __init__(self, parent_config: "CoreConfig") -> None:
@ -127,35 +195,53 @@ class BillingConfig:
@property
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
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
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
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:
def __init__(self, parent_config: "CoreConfig") -> None:
self.__config = parent_config
@property
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
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
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:
def __init__(self, parent_config: "CoreConfig") -> None:
@ -163,27 +249,42 @@ class MuchaConfig:
@property
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
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
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
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
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
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):
def __init__(self) -> None:
@ -200,20 +301,22 @@ class CoreConfig(dict):
def str_to_loglevel(cls, level_str: str):
if level_str.lower() == "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
elif level_str.lower() == "debug":
return logging.DEBUG
else:
return logging.INFO
return logging.INFO
@classmethod
def get_config_field(cls, __config: dict, module, *path: str, default: Any = "") -> Any:
envKey = f'CFG_{module}_'
def get_config_field(
cls, __config: dict, module, *path: str, default: Any = ""
) -> Any:
envKey = f"CFG_{module}_"
for arg in path:
envKey += arg + '_'
if envKey.endswith('_'):
envKey += arg + "_"
if envKey.endswith("_"):
envKey = envKey[:-1]
if envKey in os.environ:

View File

@ -1,6 +1,7 @@
from enum import Enum
class MainboardPlatformCodes():
class MainboardPlatformCodes:
RINGEDGE = "AALE"
RINGWIDE = "AAML"
NU = "AAVE"
@ -8,7 +9,8 @@ class MainboardPlatformCodes():
ALLS_UX = "ACAE"
ALLS_HX = "ACAX"
class MainboardRevisions():
class MainboardRevisions:
RINGEDGE = 1
RINGEDGE2 = 2
@ -26,12 +28,14 @@ class MainboardRevisions():
ALLS_UX2 = 2
ALLS_HX2 = 12
class KeychipPlatformsCodes():
class KeychipPlatformsCodes:
RING = "A72E"
NU = ("A60E", "A60E", "A60E")
NUSX = ("A61X", "A69X")
ALLS = "A63E"
class AllnetCountryCode(Enum):
JAPAN = "JPN"
UNITED_STATES = "USA"
@ -41,6 +45,7 @@ class AllnetCountryCode(Enum):
TAIWAN = "TWN"
CHINA = "CHN"
class AllnetJapanRegionId(Enum):
NONE = 0
AICHI = 1

View File

@ -1,2 +1,2 @@
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 functools import wraps
import hashlib
@ -6,15 +5,17 @@ import pickle
import logging
from core.config import CoreConfig
cfg:CoreConfig = None # type: ignore
cfg: CoreConfig = None # type: ignore
# Make memcache optional
try:
import pylibmc # type: ignore
has_mc = True
except ModuleNotFoundError:
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:
if has_mc:
hostname = "127.0.0.1"
@ -22,11 +23,10 @@ def cached(lifetime: int=10, extra_key: Any=None) -> Callable:
hostname = cfg.database.memcached_host
memcache = pylibmc.Client([hostname], binary=True)
memcache.behaviors = {"tcp_nodelay": True, "ketama": True}
@wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any:
if lifetime is not None:
# Hash function args
items = kwargs.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:
logging.getLogger("database").error(f"Memcache failed: {e}")
result = None
if result is not None:
logging.getLogger("database").debug(f"Cache hit: {result}")
return result
@ -55,7 +55,9 @@ def cached(lifetime: int=10, extra_key: Any=None) -> Callable:
memcache.set(cache_key, result, lifetime)
return result
else:
@wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any:
return func(*args, **kwargs)

View File

@ -13,6 +13,7 @@ from core.config import CoreConfig
from core.data.schema import *
from core.utils import Utils
class Data:
def __init__(self, cfg: CoreConfig) -> None:
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"
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.__engine = create_engine(self.__url, pool_recycle=3600)
session = sessionmaker(bind=self.__engine, autoflush=True, autocommit=True)
self.session = scoped_session(session)
@ -38,11 +39,15 @@ class Data:
self.logger = logging.getLogger("database")
# Prevent the logger from adding handlers multiple times
if not getattr(self.logger, 'handler_set', None):
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.config.server.log_dir, "db"), encoding="utf-8",
when="d", backupCount=10)
if not getattr(self.logger, "handler_set", None):
fileHandler = TimedRotatingFileHandler(
"{0}/{1}.log".format(self.config.server.log_dir, "db"),
encoding="utf-8",
when="d",
backupCount=10,
)
fileHandler.setFormatter(log_fmt)
consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt)
@ -50,8 +55,10 @@ class Data:
self.logger.addHandler(consoleHandler)
self.logger.setLevel(self.config.database.loglevel)
coloredlogs.install(cfg.database.loglevel, logger=self.logger, fmt=log_fmt_str)
self.logger.handler_set = True # type: ignore
coloredlogs.install(
cfg.database.loglevel, logger=self.logger, fmt=log_fmt_str
)
self.logger.handler_set = True # type: ignore
def create_database(self):
self.logger.info("Creating databases...")
@ -60,24 +67,32 @@ class Data:
except SQLAlchemyError as e:
self.logger.error(f"Failed to create databases! {e}")
return
games = Utils.get_all_titles()
for game_dir, game_mod in games.items():
try:
title_db = game_mod.database(self.config)
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:
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.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.user.reset_autoincrement(self.config.database.user_table_autoincrement_start)
self.logger.info(
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):
self.logger.info("Dropping all databases...")
self.base.execute("SET FOREIGN_KEY_CHECKS=0")
@ -86,61 +101,79 @@ class Data:
except SQLAlchemyError as e:
self.logger.error(f"Failed to drop databases! {e}")
return
for root, dirs, files in os.walk("./titles"):
for dir in dirs:
for dir in dirs:
if not dir.startswith("__"):
try:
mod = importlib.import_module(f"titles.{dir}")
try:
title_db = mod.database(self.config)
metadata.drop_all(self.__engine.connect())
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:
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
self.base.execute("SET FOREIGN_KEY_CHECKS=1")
self.create_database()
def migrate_database(self, game: str, version: int, action: str) -> None:
old_ver = self.base.get_schema_ver(game)
sql = ""
if old_ver is None:
self.logger.error(f"Schema for game {game} does not exist, did you run the creation script?")
return
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")
self.logger.error(
f"Schema for game {game} does not exist, did you run the creation script?"
)
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()
result = self.base.execute(sql)
if result is None:
self.logger.error("Error execuing sql script!")
return None
result = self.base.set_schema_ver(version, game)
if result is None:
self.logger.error("Error setting version in schema_version table!")
return None
self.logger.info(f"Successfully migrated {game} to schema version {version}")
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())
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}")
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:
if old_ac == new_ac:
self.logger.error("Both access codes are the same!")
return
new_card = self.card.get_card_by_access_code(new_ac)
if new_card is None:
self.card.update_access_code(old_ac, new_ac)
return
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."\
f" All exiting data on the target card {new_ac} will be perminently erased and replaced with data from card {old_ac}.")
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."
f" All exiting data on the target card {new_ac} will be perminently erased and replaced with data from card {old_ac}."
)
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.update_access_code(old_ac, new_ac)
hanging_user = self.user.get_user(new_card["user"])
if hanging_user["password"] is None:
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:
"""
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()
if unreg_users is None:
self.logger.error("Error occoured finding unregistered 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:
self.logger.error(f"Error getting cards for user {user['id']}")
continue
if not cards:
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.arcade import ArcadeData
__all__ = ["UserData", "CardData", "BaseData", "metadata", "ArcadeData"]
__all__ = ["UserData", "CardData", "BaseData", "metadata", "ArcadeData"]

View File

@ -14,131 +14,186 @@ arcade = Table(
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("name", String(255)),
Column("nickname", String(255)),
Column("nickname", String(255)),
Column("country", String(3)),
Column("country_id", Integer),
Column("state", String(255)),
Column("city", String(255)),
Column("region_id", Integer),
Column("timezone", String(255)),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
machine = Table(
"machine",
metadata,
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("board", String(15)),
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("ota_enable", Boolean),
Column("is_cab", Boolean),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
arcade_owner = Table(
'arcade_owner',
"arcade_owner",
metadata,
Column('user', Integer, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), 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'
Column(
"user",
Integer,
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
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):
def get_machine(self, serial: str = None, id: int = None) -> Optional[Dict]:
if serial is not None:
serial = serial.replace("-", "")
if len(serial) == 11:
sql = machine.select(machine.c.serial.like(f"{serial}%"))
elif len(serial) == 15:
sql = machine.select(machine.c.serial == serial)
else:
self.logger.error(f"{__name__ }: Malformed serial {serial}")
return None
elif id is not None:
sql = machine.select(machine.c.id == id)
else:
else:
self.logger.error(f"{__name__ }: Need either serial or ID to look up!")
return None
result = self.execute(sql)
if result is None: return None
if result is None:
return None
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:
self.logger.error(f"{__name__ }: Need arcade id!")
return None
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
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
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 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)
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:
serial = serial.replace("-", "")
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
platform_code = serial[:4]
platform_rev = serial[4:6]
const_a = serial[6]
@ -150,11 +205,15 @@ class ArcadeData(BaseData):
return False
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
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 True

View File

@ -19,7 +19,7 @@ schema_ver = Table(
metadata,
Column("game", String(4), primary_key=True, nullable=False),
Column("version", Integer, nullable=False, server_default="1"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
event_log = Table(
@ -32,16 +32,17 @@ event_log = Table(
Column("message", String(1000), nullable=False),
Column("details", JSON, nullable=False),
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:
self.config = cfg
self.conn = conn
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
try:
@ -51,7 +52,7 @@ class BaseData():
except SQLAlchemyError as e:
self.logger.error(f"SQLAlchemy error {e}")
return None
except UnicodeEncodeError as e:
self.logger.error(f"UnicodeEncodeError error {e}")
return None
@ -63,7 +64,7 @@ class BaseData():
except SQLAlchemyError as e:
self.logger.error(f"SQLAlchemy error {e}")
return None
except UnicodeEncodeError as e:
self.logger.error(f"UnicodeEncodeError error {e}")
return None
@ -73,58 +74,71 @@ class BaseData():
raise
return res
def generate_id(self) -> int:
"""
Generate a random 5-7 digit id
"""
return randrange(10000, 9999999)
def get_schema_ver(self, game: str) -> Optional[int]:
sql = select(schema_ver).where(schema_ver.c.game == game)
result = self.execute(sql)
if result is None:
return None
row = result.fetchone()
if row is None:
return None
return row["version"]
def set_schema_ver(self, ver: int, game: str = "CORE") -> Optional[int]:
sql = insert(schema_ver).values(game = game, version = ver)
conflict = sql.on_duplicate_key_update(version = ver)
sql = insert(schema_ver).values(game=game, version=ver)
conflict = sql.on_duplicate_key_update(version=ver)
result = self.execute(conflict)
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 result.lastrowid
def log_event(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))
def log_event(
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)
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 result.lastrowid
def get_event_log(self, entries: int = 100) -> Optional[List[Dict]]:
sql = event_log.select().limit(entries).all()
result = self.execute(sql)
if result is None: return None
if result is None:
return None
return result.fetchall()
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":
data[k] = True
elif type(v) == str and v.lower() == "false":
data[k] = False
return data

View File

@ -8,55 +8,67 @@ from sqlalchemy.engine import Row
from core.data.schema.base import BaseData, metadata
aime_card = Table(
'aime_card',
"aime_card",
metadata,
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("created_date", TIMESTAMP, server_default=func.now()),
Column("last_login_date", TIMESTAMP, onupdate=func.now()),
Column("is_locked", Boolean, server_default="0"),
Column("is_banned", Boolean, server_default="0"),
UniqueConstraint("user", "access_code", name="aime_card_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
class CardData(BaseData):
def get_card_by_access_code(self, access_code: str) -> Optional[Row]:
sql = aime_card.select(aime_card.c.access_code == access_code)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
return result.fetchone()
def get_card_by_id(self, card_id: int) -> Optional[Row]:
sql = aime_card.select(aime_card.c.id == card_id)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
return result.fetchone()
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)
if result is None:
self.logger.error(f"Failed to change card access code from {old_ac} to {new_ac}")
if result is None:
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]:
"""
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)
if card is None: return None
if card is None:
return None
return int(card["user"])
def delete_card(self, card_id: int) -> None:
sql = aime_card.delete(aime_card.c.id == card_id)
result = self.execute(sql)
if result is None:
if result is None:
self.logger.error(f"Failed to delete card with id {card_id}")
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)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
return result.fetchall()
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
"""
sql = aime_card.insert().values(user=user_id, access_code=access_code)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
return result.lastrowid
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
"""
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("email", String(255), unique=True),
Column("password", String(255)),
Column("permissions", Integer),
Column("permissions", Integer),
Column("created_date", TIMESTAMP, server_default=func.now()),
Column("last_login_date", TIMESTAMP, onupdate=func.now()),
Column("suspend_expire_time", TIMESTAMP),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
class PermissionBits(Enum):
PermUser = 1
PermMod = 2
PermSysAdmin = 4
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:
sql = insert(aime_user).values(
username=username,
email=email,
password=password,
permissions=permission
username=username,
email=email,
password=password,
permissions=permission,
)
else:
sql = insert(aime_user).values(
id=id,
username=username,
email=email,
password=password,
permissions=permission
id=id,
username=username,
email=email,
password=password,
permissions=permission,
)
conflict = sql.on_duplicate_key_update(
username=username,
email=email,
password=password,
permissions=permission
username=username, email=email, password=password, permissions=permission
)
result = self.execute(conflict)
if result is None: return None
if result is None:
return None
return result.lastrowid
def get_user(self, user_id: int) -> Optional[Row]:
sql = select(aime_user).where(aime_user.c.id == user_id)
result = self.execute(sql)
if result is None: return False
if result is None:
return False
return result.fetchone()
def check_password(self, user_id: int, passwd: bytes = None) -> bool:
usr = self.get_user(user_id)
if usr is None: return False
if usr['password'] is None:
if usr is None:
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:
# ALTER TABLE isn't in sqlalchemy so we do this the ugly way
sql = f"ALTER TABLE aime_user AUTO_INCREMENT={ai_value}"
self.execute(sql)
def delete_user(self, user_id: int) -> None:
sql = aime_user.delete(aime_user.c.id == user_id)
result = self.execute(sql)
if result is None:
if result is None:
self.logger.error(f"Failed to delete user with id {user_id}")
def get_unregistered_users(self) -> List[Row]:
@ -92,6 +101,6 @@ class UserData(BaseData):
sql = select(aime_user).where(aime_user.c.password == None)
result = self.execute(sql)
if result is None:
if result is 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.utils import Utils
class IUserSession(Interface):
userId = Attribute("User's ID")
current_ip = Attribute("User's current ip address")
permissions = Attribute("User's permission level")
@implementer(IUserSession)
class UserSession(object):
def __init__(self, session):
@ -26,10 +28,11 @@ class UserSession(object):
self.current_ip = "0.0.0.0"
self.permissions = 0
class FrontendServlet(resource.Resource):
def getChild(self, name: bytes, request: Request):
self.logger.debug(f"{request.getClientIP()} -> {name.decode()}")
if name == b'':
if name == b"":
return self
return resource.Resource.getChild(self, name, request)
@ -42,17 +45,23 @@ class FrontendServlet(resource.Resource):
self.game_list: List[Dict[str, str]] = []
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)
consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt)
self.logger.addHandler(fileHandler)
self.logger.addHandler(consoleHandler)
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)
fe_game = FE_Game(cfg, self.environment)
@ -65,18 +74,26 @@ class FrontendServlet(resource.Resource):
fe_game.putChild(game_dir.encode(), game_fe)
except:
raise
self.environment.globals["game_list"] = self.game_list
self.putChild(b"gate", FE_Gate(cfg, self.environment))
self.putChild(b"user", FE_User(cfg, self.environment))
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):
self.logger.debug(f"{request.getClientIP()} -> {request.uri.decode()}")
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):
"""
@ -84,43 +101,51 @@ class FE_Base(resource.Resource):
Initializes the environment, data, logger, config, and sets isLeaf to true
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:
self.core_config = cfg
self.data = Data(cfg)
self.logger = logging.getLogger('frontend')
self.logger = logging.getLogger("frontend")
self.environment = environment
self.nav_name = "nav_name"
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()}")
uri: str = request.uri.decode()
sesh = request.getSession()
usr_sesh = IUserSession(sesh)
if usr_sesh.userId > 0:
return redirectTo(b"/user", request)
if uri.startswith("/gate/create"):
return self.create_user(request)
if b'e' in request.args:
if b"e" in request.args:
try:
err = int(request.args[b'e'][0].decode())
err = int(request.args[b"e"][0].decode())
except:
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):
uri = request.uri.decode()
ip = request.getClientAddress().host
if uri == "/gate/gate.login":
if uri == "/gate/gate.login":
access_code: str = request.args[b"access_code"][0].decode()
passwd: bytes = request.args[b"passwd"][0]
if passwd == b"":
@ -129,26 +154,28 @@ class FE_Gate(FE_Base):
uid = self.data.card.get_user_id_from_card(access_code)
if uid is None:
return redirectTo(b"/gate?e=1", request)
if passwd is None:
sesh = self.data.user.check_password(uid)
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)
if not self.data.user.check_password(uid, passwd):
return redirectTo(b"/gate?e=1", request)
self.logger.info(f"Successful login of user {uid} at {ip}")
sesh = request.getSession()
usr_sesh = IUserSession(sesh)
usr_sesh.userId = uid
usr_sesh.current_ip = ip
return redirectTo(b"/user", request)
elif uri == "/gate/gate.create":
access_code: str = request.args[b"access_code"][0].decode()
username: str = request.args[b"username"][0]
@ -162,26 +189,33 @@ class FE_Gate(FE_Base):
salt = bcrypt.gensalt()
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:
return redirectTo(b"/gate?e=3", request)
if not self.data.user.check_password(uid, passwd.encode()):
return redirectTo(b"/gate", request)
return redirectTo(b"/user", request)
else:
return b""
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)
ac = request.args[b'ac'][0].decode()
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")
ac = request.args[b"ac"][0].decode()
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")
class FE_User(FE_Base):
def render_GET(self, request: Request):
@ -191,17 +225,20 @@ class FE_User(FE_Base):
usr_sesh = IUserSession(sesh)
if usr_sesh.userId == 0:
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):
isLeaf = False
children: Dict[str, Any] = {}
def getChild(self, name: bytes, request: Request):
if name == b'':
if name == b"":
return self
return resource.Resource.getChild(self, name, request)
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.utils import Utils
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_dir = cfg_dir
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 = 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)
consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt)
self.logger.addHandler(fileHandler)
self.logger.addHandler(consoleHandler)
self.logger.setLevel(logging.INFO)
coloredlogs.install(level=logging.INFO, logger=self.logger, fmt=log_fmt_str)
@ -35,43 +40,57 @@ class MuchaServlet:
for _, mod in all_titles.items():
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:
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:
req_dict = self.mucha_preprocess(request.content.getvalue())
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""
req = MuchaAuthRequest(req_dict)
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:
self.logger.warn(f"Unknown gameCd {req.gameCd}")
return b""
# 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)}")
return self.mucha_postprocess(vars(resp))
def handle_updatecheck(self, request: Request, _: Dict) -> bytes:
req_dict = self.mucha_preprocess(request.content.getvalue())
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""
req = MuchaUpdateRequest(req_dict)
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:
self.logger.warn(f"Unknown gameCd {req.gameCd}")
@ -86,14 +105,14 @@ class MuchaServlet:
def mucha_preprocess(self, data: bytes) -> Optional[Dict]:
try:
ret: Dict[str, Any] = {}
for x in data.decode().split('&'):
kvp = x.split('=')
for x in data.decode().split("&"):
kvp = x.split("=")
if len(kvp) == 2:
ret[kvp[0]] = kvp[1]
return ret
except:
self.logger.error(f"Error processing mucha request {data}")
return None
@ -101,7 +120,7 @@ class MuchaServlet:
def mucha_postprocess(self, data: dict) -> Optional[bytes]:
try:
urlencode = ""
for k,v in data.items():
for k, v in data.items():
urlencode += f"{k}={v}&"
return urlencode.encode()
@ -110,36 +129,44 @@ class MuchaServlet:
self.logger.error("Error processing mucha response")
return None
class MuchaAuthRequest():
class MuchaAuthRequest:
def __init__(self, request: Dict) -> None:
self.gameVer = "" 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.gameVer = (
"" 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.gameCd = "" if "gameCd" not in request else request["gameCd"]
self.boardType = "" if "boardType" not in request else request["boardType"]
self.boardId = "" if "boardId" not in request else request["boardId"]
self.mac = "" if "mac" not in request else request["mac"]
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.useToken = "" if "useToken" not in request else request["useToken"]
self.allToken = "" if "allToken" not in request else request["allToken"]
class MuchaAuthResponse():
class MuchaAuthResponse:
def __init__(self, mucha_url: str) -> None:
self.RESULTS = "001"
self.RESULTS = "001"
self.AUTH_INTERVAL = "86400"
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.CHARGE_URL = f"https://{mucha_url}/charge/"
self.CHARGE_URL = f"https://{mucha_url}/charge/"
self.FILE_URL = f"https://{mucha_url}/file/"
self.URL_1 = f"https://{mucha_url}/url1/"
self.URL_2 = f"https://{mucha_url}/url2/"
self.URL_3 = f"https://{mucha_url}/url3/"
self.PLACE_ID = "JPN123"
self.COUNTRY_CD = "JPN"
self.PLACE_ID = "JPN123"
self.COUNTRY_CD = "JPN"
self.SHOP_NAME = "TestShop!"
self.SHOP_NICKNAME = "TestShop"
self.AREA_0 = "008"
@ -150,7 +177,7 @@ class MuchaAuthResponse():
self.AREA_FULL_1 = ""
self.AREA_FULL_2 = ""
self.AREA_FULL_3 = ""
self.SHOP_NAME_EN = "TestShop!"
self.SHOP_NICKNAME_EN = "TestShop"
self.AREA_0_EN = "008"
@ -162,23 +189,27 @@ class MuchaAuthResponse():
self.AREA_FULL_2_EN = ""
self.AREA_FULL_3_EN = ""
self.PREFECTURE_ID = "1"
self.PREFECTURE_ID = "1"
self.EXPIRATION_DATE = "null"
self.USE_TOKEN = "0"
self.CONSUME_TOKEN = "0"
self.DONGLE_FLG = "1"
self.FORCE_BOOT = "0"
class MuchaUpdateRequest():
class MuchaUpdateRequest:
def __init__(self, request: Dict) -> None:
self.gameVer = "" if "gameVer" not in request else request["gameVer"]
self.gameCd = "" if "gameCd" not in request else request["gameCd"]
self.serialNum = "" if "serialNum" not in request else request["serialNum"]
self.countryCd = "" if "countryCd" not in request else request["countryCd"]
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:
self.RESULTS = "001"
self.UPDATE_VER_1 = game_ver
@ -194,7 +225,8 @@ class MuchaUpdateResponse():
self.USER_ID = ""
self.PASSWORD = ""
class MuchaUpdateResponseStub():
class MuchaUpdateResponseStub:
def __init__(self, game_ver: str) -> None:
self.RESULTS = "001"
self.UPDATE_VER_1 = game_ver

View File

@ -7,8 +7,9 @@ from core.config import CoreConfig
from core.data import Data
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__()
self.config = core_cfg
self.config_folder = cfg_folder
@ -18,47 +19,57 @@ class TitleServlet():
self.logger = logging.getLogger("title")
if not hasattr(self.logger, "initialized"):
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)
consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt)
self.logger.addHandler(fileHandler)
self.logger.addHandler(consoleHandler)
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
plugins = Utils.get_all_titles()
for folder, mod in plugins.items():
if hasattr(mod, "game_codes") and hasattr(mod, "index"):
should_call_setup = True
if hasattr(mod.index, "get_allnet_info"):
if hasattr(mod.index, "get_allnet_info"):
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:
handler_cls = mod.index(self.config, self.config_folder)
if hasattr(handler_cls, "setup") and should_call_setup:
handler_cls.setup()
should_call_setup = False
self.title_registry[code] = handler_cls
else:
self.logger.warn(f"Game {folder} has no get_allnet_info")
else:
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:
code = endpoints["game"]
@ -66,7 +77,7 @@ class TitleServlet():
self.logger.warn(f"Unknown game code {code}")
request.setResponseCode(404)
return b""
index = self.title_registry[code]
if not hasattr(index, "render_GET"):
self.logger.warn(f"{code} does not dispatch GET")
@ -74,18 +85,20 @@ class TitleServlet():
return b""
return index.render_GET(request, endpoints["version"], endpoints["endpoint"])
def render_POST(self, request: Request, endpoints: dict) -> bytes:
code = endpoints["game"]
if code not in self.title_registry:
self.logger.warn(f"Unknown game code {code}")
request.setResponseCode(404)
return b""
index = self.title_registry[code]
if not hasattr(index, "render_POST"):
self.logger.warn(f"{code} does not dispatch POST")
request.setResponseCode(405)
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
from os import walk
class Utils:
@classmethod
def get_all_titles(cls) -> Dict[str, ModuleType]:
ret: Dict[str, Any] = {}
for root, dirs, files in walk("titles"):
for dir in dirs:
for dir in dirs:
if not dir.startswith("__"):
try:
mod = importlib.import_module(f"titles.{dir}")

View File

@ -4,16 +4,30 @@ from core.config import CoreConfig
from core.data import Data
from os import path
if __name__=='__main__':
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Database utilities")
parser.add_argument("--config", "-c", type=str, help="Config folder to use", default="config")
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(
"--config", "-c", type=str, help="Config folder to use", default="config"
)
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("--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("--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()
cfg = CoreConfig()
@ -23,7 +37,7 @@ if __name__=='__main__':
if args.action == "create":
data.create_database()
elif args.action == "recreate":
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 routes import Mapper
class HttpDispatcher(resource.Resource):
def __init__(self, cfg: CoreConfig, config_dir: str):
super().__init__()
@ -20,73 +21,142 @@ class HttpDispatcher(resource.Resource):
self.map_get = Mapper()
self.map_post = Mapper()
self.logger = logging.getLogger("core")
self.allnet = AllnetServlet(cfg, config_dir)
self.title = TitleServlet(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('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(
"allnet_ping",
"/naomitest.html",
controller="allnet",
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('mucha_updatacheck', '/mucha/updatacheck.do', controller="mucha", action='handle_updatacheck', 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(
"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_post.connect("title_post", "/{game}/{version}/{endpoint:.*?}", controller="title", action="render_POST", conditions=dict(method=['POST']), requirements=dict(game=R"S..."))
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_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())
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)
return b"Endpoint not found."
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())
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)
return b"Endpoint not found."
return self.dispatch(test, request)
def dispatch(self, matcher: Dict, request: Request) -> bytes:
controller = getattr(self, matcher["controller"], 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)
return b"Endpoint not found."
handler = getattr(controller, matcher["action"], 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)
return b"Endpoint not found."
url_vars = matcher
url_vars.pop("controller")
url_vars.pop("action")
ret = handler(request, url_vars)
if type(ret) == str:
return ret.encode()
elif type(ret) == bytes:
return ret
else:
return b""
if __name__ == "__main__":
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()
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)
cfg: CoreConfig = CoreConfig()
@ -95,56 +165,74 @@ if __name__ == "__main__":
logger = logging.getLogger("core")
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)
consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt)
logger.addHandler(fileHandler)
logger.addHandler(consoleHandler)
log_lv = logging.DEBUG if cfg.server.is_develop else logging.INFO
logger.setLevel(log_lv)
coloredlogs.install(level=log_lv, logger=logger, fmt=log_fmt_str)
if not path.exists(cfg.server.log_dir):
mkdir(cfg.server.log_dir)
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)
if not cfg.aimedb.key:
logger.error("!!AIMEDB KEY BLANK, SET KEY IN CORE.YAML!!")
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}"
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}"
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}"
)
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))
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:
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))
reactor.run() # type: ignore
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)
)
reactor.run() # type: ignore

72
read.py
View File

@ -12,56 +12,64 @@ from typing import List, Optional
from core import CoreConfig
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.config = config
self.bin_dir = bin_dir
self.opt_dir = opt_dir
self.version = version
self.extra = extra
def get_data_directories(self, directory: str) -> List[str]:
ret: List[str] = []
for root, dirs, files in os.walk(directory):
for dir in dirs:
if re.fullmatch("[A-Z0-9]{4,4}", dir) is not None:
ret.append(f"{root}/{dir}")
for dir in dirs:
if re.fullmatch("[A-Z0-9]{4,4}", dir) is not None:
ret.append(f"{root}/{dir}")
return ret
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Import Game Information')
parser = argparse.ArgumentParser(description="Import Game Information")
parser.add_argument(
'--series',
action='store',
"--series",
action="store",
type=str,
required=True,
help='The game series we are importing.',
help="The game series we are importing.",
)
parser.add_argument(
'--version',
dest='version',
action='store',
"--version",
dest="version",
action="store",
type=int,
required=True,
help='The game version we are importing.',
help="The game version we are importing.",
)
parser.add_argument(
'--binfolder',
dest='bin',
action='store',
"--binfolder",
dest="bin",
action="store",
type=str,
help='Folder containing A000 base data',
help="Folder containing A000 base data",
)
parser.add_argument(
'--optfolder',
dest='opt',
action='store',
"--optfolder",
dest="opt",
action="store",
type=str,
help='Folder containing Option data folders',
help="Folder containing Option data folders",
)
parser.add_argument(
"--config",
@ -86,15 +94,17 @@ if __name__ == "__main__":
log_fmt = logging.Formatter(log_fmt_str)
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)
consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt)
logger.addHandler(fileHandler)
logger.addHandler(consoleHandler)
log_lv = logging.DEBUG if config.server.is_develop else logging.INFO
logger.setLevel(log_lv)
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:
logger.error("Game or version not specified")
parser.print_help()
exit(1)
exit(1)
if args.bin is None and args.opt is None:
logger.error("Must specify either bin or opt directory")
parser.print_help()
@ -113,7 +123,7 @@ if __name__ == "__main__":
bin_arg = args.bin[:-1]
else:
bin_arg = args.bin
if args.opt is not None and (args.opt.endswith("\\") or args.opt.endswith("/")):
opt_arg = args.opt[:-1]
else:
@ -127,5 +137,5 @@ if __name__ == "__main__":
if args.series in mod.game_codes:
handler = mod.reader(config, args.version, bin_arg, opt_arg, args.extra)
handler.read()
logger.info("Done")

View File

@ -5,12 +5,13 @@ from titles.chuni.base import ChuniBase
from titles.chuni.const import ChuniConstants
from titles.chuni.config import ChuniConfig
class ChuniAir(ChuniBase):
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_AIR
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"
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.config import ChuniConfig
class ChuniAirPlus(ChuniBase):
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_AIR_PLUS
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"
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.config import ChuniConfig
class ChuniAmazon(ChuniBase):
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_AMAZON
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"
return ret

View File

@ -7,12 +7,13 @@ from titles.chuni.base import ChuniBase
from titles.chuni.const import ChuniConstants
from titles.chuni.config import ChuniConfig
class ChuniAmazonPlus(ChuniBase):
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_AMAZON_PLUS
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"
return ret

View File

@ -11,7 +11,8 @@ from titles.chuni.const import ChuniConstants
from titles.chuni.database import ChuniData
from titles.chuni.config import ChuniConfig
class ChuniBase():
class ChuniBase:
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
self.core_cfg = core_cfg
self.game_cfg = game_cfg
@ -22,32 +23,31 @@ class ChuniBase():
self.version = ChuniConstants.VER_CHUNITHM
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"]})
return { "returnCode": 1 }
# self.data.base.log_event("chuni", "login", logging.INFO, {"version": self.version, "user": data["userId"]})
return {"returnCode": 1}
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"]})
return { "returnCode": 1 }
# self.data.base.log_event("chuni", "logout", logging.INFO, {"version": self.version, "user": data["userId"]})
return {"returnCode": 1}
def handle_get_game_charge_api_request(self, data: Dict) -> Dict:
game_charge_list = self.data.static.get_enabled_charges(self.version)
charges = []
for x in range(len(game_charge_list)):
charges.append({
"orderId": x,
"chargeId": game_charge_list[x]["chargeId"],
"price": 1,
"startDate": "2017-12-05 07:00:00.0",
"endDate": "2099-12-31 00:00:00.0",
"salePrice": 1,
"saleStartDate": "2017-12-05 07:00:00.0",
"saleEndDate": "2099-12-31 00:00:00.0"
})
return {
"length": len(charges),
"gameChargeList": charges
}
for x in range(len(game_charge_list)):
charges.append(
{
"orderId": x,
"chargeId": game_charge_list[x]["chargeId"],
"price": 1,
"startDate": "2017-12-05 07:00:00.0",
"endDate": "2099-12-31 00:00:00.0",
"salePrice": 1,
"saleStartDate": "2017-12-05 07:00:00.0",
"saleEndDate": "2099-12-31 00:00:00.0",
}
)
return {"length": len(charges), "gameChargeList": charges}
def handle_get_game_event_api_request(self, data: Dict) -> Dict:
game_events = self.data.static.get_enabled_events(self.version)
@ -62,26 +62,30 @@ class ChuniBase():
event_list.append(tmp)
return {
"type": data["type"],
"length": len(event_list),
"gameEventList": event_list
"type": data["type"],
"length": len(event_list),
"gameEventList": event_list,
}
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:
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:
return { "type": data["type"], "gameRankingList": [] }
return {"type": data["type"], "gameRankingList": []}
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:
reboot_start = datetime.strftime(datetime.now() - timedelta(hours=4), self.date_time_format)
reboot_end = datetime.strftime(datetime.now() - timedelta(hours=3), self.date_time_format)
reboot_start = datetime.strftime(
datetime.now() - timedelta(hours=4), self.date_time_format
)
reboot_end = datetime.strftime(
datetime.now() - timedelta(hours=3), self.date_time_format
)
return {
"gameSetting": {
"dataVersion": "1.00.00",
@ -94,15 +98,17 @@ class ChuniBase():
"maxCountItem": 300,
"maxCountMusic": 300,
},
"isDumpUpload": "false",
"isAou": "false",
"isDumpUpload": "false",
"isAou": "false",
}
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 = []
for activity in user_activity_list:
tmp = activity._asdict()
tmp.pop("user")
@ -111,15 +117,16 @@ class ChuniBase():
activity_list.append(tmp)
return {
"userId": data["userId"],
"length": len(activity_list),
"kind": data["kind"],
"userActivityList": activity_list
"userId": data["userId"],
"length": len(activity_list),
"kind": data["kind"],
"userActivityList": activity_list,
}
def handle_get_user_character_api_request(self, data: Dict) -> Dict:
characters = self.data.item.get_characters(data["userId"])
if characters is None: return {}
if characters is None:
return {}
next_idx = -1
characterList = []
@ -131,15 +138,17 @@ class ChuniBase():
if len(characterList) >= int(data["maxCount"]):
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
return {
"userId": data["userId"],
"userId": data["userId"],
"length": len(characterList),
"nextIndex": next_idx,
"userCharacterList": characterList
"nextIndex": next_idx,
"userCharacterList": characterList,
}
def handle_get_user_charge_api_request(self, data: Dict) -> Dict:
@ -153,21 +162,21 @@ class ChuniBase():
charge_list.append(tmp)
return {
"userId": data["userId"],
"userId": data["userId"],
"length": len(charge_list),
"userChargeList": charge_list
"userChargeList": charge_list,
}
def handle_get_user_course_api_request(self, data: Dict) -> Dict:
user_course_list = self.data.score.get_courses(data["userId"])
if user_course_list is None:
if user_course_list is None:
return {
"userId": data["userId"],
"userId": data["userId"],
"length": 0,
"nextIndex": -1,
"userCourseList": []
"nextIndex": -1,
"userCourseList": [],
}
course_list = []
next_idx = int(data["nextIndex"])
max_ct = int(data["maxCount"])
@ -180,51 +189,48 @@ class ChuniBase():
if len(user_course_list) >= max_ct:
break
if len(user_course_list) >= max_ct:
next_idx = next_idx + max_ct
else:
next_idx = -1
return {
"userId": data["userId"],
"userId": data["userId"],
"length": len(course_list),
"nextIndex": next_idx,
"userCourseList": course_list
"nextIndex": next_idx,
"userCourseList": course_list,
}
def handle_get_user_data_api_request(self, data: Dict) -> Dict:
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.pop("id")
profile.pop("user")
profile.pop("version")
return {
"userId": data["userId"],
"userData": profile
}
return {"userId": data["userId"], "userData": profile}
def handle_get_user_data_ex_api_request(self, data: Dict) -> Dict:
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.pop("id")
profile.pop("user")
profile.pop("version")
return {
"userId": data["userId"],
"userDataEx": profile
}
return {"userId": data["userId"], "userDataEx": profile}
def handle_get_user_duel_api_request(self, data: Dict) -> Dict:
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 = []
for duel in user_duel_list:
tmp = duel._asdict()
@ -233,18 +239,18 @@ class ChuniBase():
duel_list.append(tmp)
return {
"userId": data["userId"],
"userId": data["userId"],
"length": len(duel_list),
"userDuelList": duel_list
"userDuelList": duel_list,
}
def handle_get_user_favorite_item_api_request(self, data: Dict) -> Dict:
return {
"userId": data["userId"],
"userId": data["userId"],
"length": 0,
"kind": data["kind"],
"nextIndex": -1,
"userFavoriteItemList": []
"kind": data["kind"],
"nextIndex": -1,
"userFavoriteItemList": [],
}
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
"""
return {
"userId": data["userId"],
"length": 0,
"userFavoriteMusicList": []
}
return {"userId": data["userId"], "length": 0, "userFavoriteMusicList": []}
def handle_get_user_item_api_request(self, data: Dict) -> Dict:
kind = int(int(data["nextIndex"]) / 10000000000)
next_idx = int(int(data["nextIndex"]) % 10000000000)
user_item_list = self.data.item.get_items(data["userId"], kind)
if user_item_list is None or len(user_item_list) == 0:
return {"userId": data["userId"], "nextIndex": -1, "itemKind": kind, "userItemList": []}
if user_item_list is None or len(user_item_list) == 0:
return {
"userId": data["userId"],
"nextIndex": -1,
"itemKind": kind,
"userItemList": [],
}
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.pop("user")
tmp.pop("id")
@ -277,38 +284,47 @@ class ChuniBase():
xout = kind * 10000000000 + next_idx + len(items)
if len(items) < int(data["maxCount"]): nextIndex = 0
else: nextIndex = xout
if len(items) < int(data["maxCount"]):
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:
"""
Unsure how to get this to trigger...
"""
return {
"userId": data["userId"],
"userId": data["userId"],
"length": 2,
"userLoginBonusList": [
{
"presetId": '10',
"bonusCount": '0',
"lastUpdateDate": "1970-01-01 09:00:00",
"isWatched": "true"
"presetId": "10",
"bonusCount": "0",
"lastUpdateDate": "1970-01-01 09:00:00",
"isWatched": "true",
},
{
"presetId": '20',
"bonusCount": '0',
"lastUpdateDate": "1970-01-01 09:00:00",
"isWatched": "true"
"presetId": "20",
"bonusCount": "0",
"lastUpdateDate": "1970-01-01 09:00:00",
"isWatched": "true",
},
]
],
}
def handle_get_user_map_api_request(self, data: Dict) -> Dict:
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 = []
for map in user_map_list:
tmp = map._asdict()
@ -317,19 +333,19 @@ class ChuniBase():
map_list.append(tmp)
return {
"userId": data["userId"],
"userId": data["userId"],
"length": len(map_list),
"userMapList": map_list
"userMapList": map_list,
}
def handle_get_user_music_api_request(self, data: Dict) -> Dict:
music_detail = self.data.score.get_scores(data["userId"])
if music_detail is None:
if music_detail is None:
return {
"userId": data["userId"],
"length": 0,
"userId": data["userId"],
"length": 0,
"nextIndex": -1,
"userMusicList": [] #240
"userMusicList": [], # 240
}
song_list = []
next_idx = int(data["nextIndex"])
@ -340,66 +356,60 @@ class ChuniBase():
tmp = music_detail[x]._asdict()
tmp.pop("user")
tmp.pop("id")
for song in song_list:
if song["userMusicDetailList"][0]["musicId"] == tmp["musicId"]:
found = True
song["userMusicDetailList"].append(tmp)
song["length"] = len(song["userMusicDetailList"])
if not found:
song_list.append({
"length": 1,
"userMusicDetailList": [tmp]
})
song_list.append({"length": 1, "userMusicDetailList": [tmp]})
if len(song_list) >= max_ct:
break
if len(song_list) >= max_ct:
next_idx += max_ct
else:
next_idx = 0
return {
"userId": data["userId"],
"length": len(song_list),
"userId": data["userId"],
"length": len(song_list),
"nextIndex": next_idx,
"userMusicList": song_list #240
"userMusicList": song_list, # 240
}
def handle_get_user_option_api_request(self, data: Dict) -> Dict:
p = self.data.profile.get_profile_option(data["userId"])
option = p._asdict()
option.pop("id")
option.pop("user")
return {
"userId": data["userId"],
"userGameOption": option
}
return {"userId": data["userId"], "userGameOption": option}
def handle_get_user_option_ex_api_request(self, data: Dict) -> Dict:
p = self.data.profile.get_profile_option_ex(data["userId"])
option = p._asdict()
option.pop("id")
option.pop("user")
return {
"userId": data["userId"],
"userGameOptionEx": option
}
return {"userId": data["userId"], "userGameOptionEx": option}
def read_wtf8(self, src):
return bytes([ord(c) for c in src]).decode("utf-8")
def handle_get_user_preview_api_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile_preview(data["userId"], self.version)
if profile is None: return None
profile_character = self.data.item.get_character(data["userId"], profile["characterId"])
if profile is None:
return None
profile_character = self.data.item.get_character(
data["userId"], profile["characterId"]
)
if profile_character is None:
chara = {}
else:
@ -408,8 +418,8 @@ class ChuniBase():
chara.pop("user")
return {
"userId": data["userId"],
# Current Login State
"userId": data["userId"],
# Current Login State
"isLogin": False,
"lastLoginDate": profile["lastPlayDate"],
# User Profile
@ -421,14 +431,14 @@ class ChuniBase():
"lastGameId": profile["lastGameId"],
"lastRomVersion": profile["lastRomVersion"],
"lastDataVersion": profile["lastDataVersion"],
"lastPlayDate": profile["lastPlayDate"],
"trophyId": profile["trophyId"],
"lastPlayDate": profile["lastPlayDate"],
"trophyId": profile["trophyId"],
"nameplateId": profile["nameplateId"],
# Current Selected Character
"userCharacter": chara,
# User Game Options
"playerLevel": profile["playerLevel"],
"rating": profile["rating"],
"playerLevel": profile["playerLevel"],
"rating": profile["rating"],
"headphone": profile["headphone"],
"chargeState": "1",
"userNameEx": profile["userName"],
@ -436,7 +446,7 @@ class ChuniBase():
def handle_get_user_recent_rating_api_request(self, data: Dict) -> Dict:
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 {
"userId": data["userId"],
"length": 0,
@ -459,11 +469,8 @@ class ChuniBase():
def handle_get_user_team_api_request(self, data: Dict) -> Dict:
# TODO: Team
return {
"userId": data["userId"],
"teamId": 0
}
return {"userId": data["userId"], "teamId": 0}
def handle_get_team_course_setting_api_request(self, data: Dict) -> Dict:
return {
"userId": data["userId"],
@ -486,19 +493,30 @@ class ChuniBase():
if "userData" in upsert:
try:
upsert["userData"][0]["userName"] = self.read_wtf8(upsert["userData"][0]["userName"])
except: pass
upsert["userData"][0]["userName"] = self.read_wtf8(
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:
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:
self.data.profile.put_profile_option(user_id, upsert["userGameOption"][0])
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:
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:
for character in upsert["userCharacterList"]:
self.data.item.put_character(user_id, character)
@ -514,7 +532,7 @@ class ChuniBase():
if "userDuelList" in upsert:
for duel in upsert["userDuelList"]:
self.data.item.put_duel(user_id, duel)
if "userItemList" in upsert:
for item in upsert["userItemList"]:
self.data.item.put_item(user_id, item)
@ -522,23 +540,23 @@ class ChuniBase():
if "userActivityList" in upsert:
for activity in upsert["userActivityList"]:
self.data.profile.put_profile_activity(user_id, activity)
if "userChargeList" in upsert:
for charge in upsert["userChargeList"]:
self.data.profile.put_profile_charge(user_id, charge)
if "userMusicDetailList" in upsert:
for song in upsert["userMusicDetailList"]:
self.data.score.put_score(user_id, song)
if "userPlaylogList" in upsert:
for playlog in upsert["userPlaylogList"]:
self.data.score.put_playlog(user_id, playlog)
if "userTeamPoint" in upsert:
# TODO: team stuff
pass
if "userMapAreaList" in upsert:
for map_area in upsert["userMapAreaList"]:
self.data.item.put_map_area(user_id, map_area)
@ -551,22 +569,22 @@ class ChuniBase():
for emoney in upsert["userEmoneyList"]:
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:
return { "returnCode": "1" }
return {"returnCode": "1"}
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:
return { "returnCode": "1" }
return {"returnCode": "1"}
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:
return { "returnCode": "1" }
return {"returnCode": "1"}
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 typing import Dict
class ChuniServerConfig():
class ChuniServerConfig:
def __init__(self, parent_config: "ChuniConfig") -> None:
self.__config = parent_config
@property
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
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:
self.__config = parent_config
@property
def keys(self) -> Dict:
"""
in the form of:
internal_version: [key, iv]
internal_version: [key, iv]
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
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):
def __init__(self) -> None:
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_NEW = "SDHD"
@ -8,7 +8,7 @@ class ChuniConstants():
VER_CHUNITHM_PLUS = 1
VER_CHUNITHM_AIR = 2
VER_CHUNITHM_AIR_PLUS = 3
VER_CHUNITHM_STAR = 4
VER_CHUNITHM_STAR = 4
VER_CHUNITHM_STAR_PLUS = 5
VER_CHUNITHM_AMAZON = 6
VER_CHUNITHM_AMAZON_PLUS = 7
@ -18,8 +18,21 @@ class ChuniConstants():
VER_CHUNITHM_NEW = 11
VER_CHUNITHM_NEW_PLUS = 12
VERSION_NAMES = ["Chunithm", "Chunithm+", "Chunithm Air", "Chunithm Air+", "Chunithm Star", "Chunithm Star+", "Chunithm Amazon",
"Chunithm Amazon+", "Chunithm Crystal", "Chunithm Crystal+", "Chunithm Paradise", "Chunithm New!!", "Chunithm New!!+"]
VERSION_NAMES = [
"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
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.config import ChuniConfig
class ChuniCrystal(ChuniBase):
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_CRYSTAL
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"
return ret

View File

@ -7,12 +7,13 @@ from titles.chuni.base import ChuniBase
from titles.chuni.const import ChuniConstants
from titles.chuni.config import ChuniConfig
class ChuniCrystalPlus(ChuniBase):
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_CRYSTAL_PLUS
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"
return ret

View File

@ -2,6 +2,7 @@ from core.data import Data
from core.config import CoreConfig
from titles.chuni.schema import *
class ChuniData(Data):
def __init__(self, cfg: CoreConfig) -> None:
super().__init__(cfg)
@ -9,4 +10,4 @@ class ChuniData(Data):
self.item = ChuniItemData(cfg, self.session)
self.profile = ChuniProfileData(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.newplus import ChuniNewPlus
class ChuniServlet():
class ChuniServlet:
def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None:
self.core_cfg = core_cfg
self.game_cfg = ChuniConfig()
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 = [
ChuniBase(core_cfg, self.game_cfg),
@ -56,33 +59,47 @@ class ChuniServlet():
if not hasattr(self.logger, "inited"):
log_fmt_str = "[%(asctime)s] Chunithm | %(levelname)s | %(message)s"
log_fmt = logging.Formatter(log_fmt_str)
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.core_cfg.server.log_dir, "chuni"), encoding='utf8',
when="d", backupCount=10)
fileHandler = TimedRotatingFileHandler(
"{0}/{1}.log".format(self.core_cfg.server.log_dir, "chuni"),
encoding="utf8",
when="d",
backupCount=10,
)
fileHandler.setFormatter(log_fmt)
consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt)
self.logger.addHandler(fileHandler)
self.logger.addHandler(consoleHandler)
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
@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()
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:
return (False, "", "")
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/", "")
def render_POST(self, request: Request, version: int, url_path: str) -> bytes:
@ -95,67 +112,75 @@ class ChuniServlet():
internal_ver = 0
endpoint = url_split[len(url_split) - 1]
if version < 105: # 1.0
if version < 105: # 1.0
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
elif version >= 110 and version < 115: # Air
elif version >= 110 and version < 115: # 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
elif version >= 120 and version < 125: # Star
elif version >= 120 and version < 125: # 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
elif version >= 130 and version < 135: # Amazon
elif version >= 130 and version < 135: # 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
elif version >= 140 and version < 145: # Crystal
elif version >= 140 and version < 145: # 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
elif version >= 150 and version < 200: # Paradise
elif version >= 150 and version < 200: # 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
elif version >= 205 and version < 210: # New Plus
elif version >= 205 and version < 210: # New Plus
internal_ver = ChuniConstants.VER_CHUNITHM_NEW_PLUS
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
# doing encrypted. The likelyhood of false positives is low but
# 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
# technically not 0
endpoint = request.getHeader("User-Agent").split("#")[0]
try:
crypt = AES.new(
bytes.fromhex(self.game_cfg.crypto.keys[str(internal_ver)][0]),
AES.MODE_CBC,
bytes.fromhex(self.game_cfg.crypto.keys[str(internal_ver)][1])
bytes.fromhex(self.game_cfg.crypto.keys[str(internal_ver)][0]),
AES.MODE_CBC,
bytes.fromhex(self.game_cfg.crypto.keys[str(internal_ver)][1]),
)
req_raw = crypt.decrypt(req_raw)
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"}')
encrtped = True
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"}')
try:
try:
unzip = zlib.decompress(req_raw)
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""
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)
func_to_find = "handle_" + inflection.underscore(endpoint) + "_request"
@ -163,9 +188,8 @@ class ChuniServlet():
if not hasattr(self.versions[internal_ver], func_to_find):
self.logger.warning(f"Unhandled v{version} request {endpoint}")
resp = {"returnCode": 1}
else:
else:
try:
handler = getattr(self.versions[internal_ver], func_to_find)
resp = handler(req_data)
@ -173,23 +197,23 @@ class ChuniServlet():
except Exception as e:
self.logger.error(f"Error handling v{version} method {endpoint} - {e}")
return zlib.compress(b'{"stat": "0"}')
if resp == None:
resp = {'returnCode': 1}
resp = {"returnCode": 1}
self.logger.debug(f"Response {resp}")
zipped = zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8"))
if not encrtped:
return zipped
padded = pad(zipped, 16)
crypt = AES.new(
bytes.fromhex(self.game_cfg.crypto.keys[str(internal_ver)][0]),
AES.MODE_CBC,
bytes.fromhex(self.game_cfg.crypto.keys[str(internal_ver)][1])
bytes.fromhex(self.game_cfg.crypto.keys[str(internal_ver)][0]),
AES.MODE_CBC,
bytes.fromhex(self.game_cfg.crypto.keys[str(internal_ver)][1]),
)
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.config import ChuniConfig
class ChuniNew(ChuniBase):
ITEM_TYPE = {
"character": 20,
"story": 21,
"card": 22
}
class ChuniNew(ChuniBase):
ITEM_TYPE = {"character": 20, "story": 21, "card": 22}
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
self.core_cfg = core_cfg
@ -25,12 +21,20 @@ class ChuniNew(ChuniBase):
self.logger = logging.getLogger("chuni")
self.game = ChuniConstants.GAME_CODE
self.version = ChuniConstants.VER_CHUNITHM_NEW
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_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)
match_start = 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 {
"gameSetting": {
"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/",
"reflectorUri": f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/200/ChuniServlet/",
},
"isDumpUpload": "false",
"isAou": "false",
"isDumpUpload": "false",
"isAou": "false",
}
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:
return { "returnCode": "1" }
return {"returnCode": "1"}
def handle_get_user_map_area_api_request(self, data: Dict) -> Dict:
user_map_areas = self.data.item.get_map_areas(data["userId"])
@ -72,32 +76,29 @@ class ChuniNew(ChuniBase):
tmp.pop("user")
map_areas.append(tmp)
return {
"userId": data["userId"],
"userMapAreaList": map_areas
}
return {"userId": data["userId"], "userMapAreaList": map_areas}
def handle_get_user_symbol_chat_setting_api_request(self, data: Dict) -> Dict:
return {
"userId": data["userId"],
"symbolCharInfoList": []
}
return {"userId": data["userId"], "symbolCharInfoList": []}
def handle_get_user_preview_api_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile_preview(data["userId"], self.version)
if profile is None: return None
profile_character = self.data.item.get_character(data["userId"], profile["characterId"])
if profile is None:
return None
profile_character = self.data.item.get_character(
data["userId"], profile["characterId"]
)
if profile_character is None:
chara = {}
else:
chara = profile_character._asdict()
chara.pop("id")
chara.pop("user")
data1 = {
"userId": data["userId"],
# Current Login State
"userId": data["userId"],
# Current Login State
"isLogin": False,
"lastLoginDate": profile["lastPlayDate"],
# User Profile
@ -109,14 +110,14 @@ class ChuniNew(ChuniBase):
"lastGameId": profile["lastGameId"],
"lastRomVersion": profile["lastRomVersion"],
"lastDataVersion": profile["lastDataVersion"],
"lastPlayDate": profile["lastPlayDate"],
"lastPlayDate": profile["lastPlayDate"],
"emoneyBrandId": 0,
"trophyId": profile["trophyId"],
"trophyId": profile["trophyId"],
# Current Selected Character
"userCharacter": chara,
# User Game Options
"playerLevel": profile["playerLevel"],
"rating": profile["rating"],
"playerLevel": profile["playerLevel"],
"rating": profile["rating"],
"headphone": profile["headphone"],
"chargeState": 0,
"userNameEx": "0",

View File

@ -7,17 +7,26 @@ from titles.chuni.new import ChuniNew
from titles.chuni.const import ChuniConstants
from titles.chuni.config import ChuniConfig
class ChuniNewPlus(ChuniNew):
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_NEW_PLUS
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["romVersion"] = "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"]["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/"
ret["gameSetting"][
"matchingUri"
] = 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

View File

@ -7,12 +7,13 @@ from titles.chuni.base import ChuniBase
from titles.chuni.const import ChuniConstants
from titles.chuni.config import ChuniConfig
class ChuniParadise(ChuniBase):
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_PARADISE
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"
return ret

View File

@ -5,12 +5,13 @@ from titles.chuni.base import ChuniBase
from titles.chuni.const import ChuniConstants
from titles.chuni.config import ChuniConfig
class ChuniPlus(ChuniBase):
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_PLUS
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"
return ret
return ret

View File

@ -7,48 +7,60 @@ from core.config import CoreConfig
from titles.chuni.database import ChuniData
from titles.chuni.const import ChuniConstants
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)
self.data = ChuniData(config)
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:
self.logger.error(f"Invalid chunithm version {version}")
exit(1)
def read(self) -> None:
data_dirs = []
if self.bin_dir is not None:
data_dirs += self.get_data_directories(self.bin_dir)
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:
self.logger.info(f"Read from {dir}")
self.read_events(f"{dir}/event")
self.read_music(f"{dir}/music")
self.read_charges(f"{dir}/chargeItem")
self.read_avatar(f"{dir}/avatarAccessory")
def read_events(self, evt_dir: str) -> None:
for root, dirs, files in walk(evt_dir):
for dir in dirs:
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()
strdata = bytedata.decode('UTF-8')
strdata = bytedata.decode("UTF-8")
xml_root = ET.fromstring(strdata)
for name in xml_root.findall('name'):
id = name.find('id').text
name = name.find('str').text
for substances in xml_root.findall('substances'):
event_type = substances.find('type').text
result = self.data.static.put_event(self.version, id, event_type, name)
for name in xml_root.findall("name"):
id = name.find("id").text
name = name.find("str").text
for substances in xml_root.findall("substances"):
event_type = substances.find("type").text
result = self.data.static.put_event(
self.version, id, event_type, name
)
if result is not None:
self.logger.info(f"Inserted event {id}")
else:
@ -58,73 +70,90 @@ class ChuniReader(BaseReader):
for root, dirs, files in walk(music_dir):
for dir in dirs:
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()
strdata = bytedata.decode('UTF-8')
strdata = bytedata.decode("UTF-8")
xml_root = ET.fromstring(strdata)
for name in xml_root.findall('name'):
song_id = name.find('id').text
title = name.find('str').text
for name in xml_root.findall("name"):
song_id = name.find("id").text
title = name.find("str").text
for artistName in xml_root.findall('artistName'):
artist = artistName.find('str').text
for artistName in xml_root.findall("artistName"):
artist = artistName.find("str").text
for genreNames in xml_root.findall('genreNames'):
for list_ in genreNames.findall('list'):
for StringID in list_.findall('StringID'):
genre = StringID.find('str').text
for genreNames in xml_root.findall("genreNames"):
for list_ in genreNames.findall("list"):
for StringID in list_.findall("StringID"):
genre = StringID.find("str").text
for jaketFile in xml_root.findall('jaketFile'): #nice typo, SEGA
jacket_path = jaketFile.find('path').text
for jaketFile in xml_root.findall("jaketFile"): # nice typo, SEGA
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:
chart_id = MusicFumenData.find('type').find('id').text
chart_id = MusicFumenData.find("type").find("id").text
if chart_id == "4":
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:
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
result = self.data.static.put_music(
self.version,
song_id,
chart_id,
chart_id,
title,
artist,
level,
genre,
jacket_path,
we_chara
we_chara,
)
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:
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:
for root, dirs, files in walk(charge_dir):
for dir in dirs:
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()
strdata = bytedata.decode('UTF-8')
strdata = bytedata.decode("UTF-8")
xml_root = ET.fromstring(strdata)
for name in xml_root.findall('name'):
id = name.find('id').text
name = name.find('str').text
expirationDays = xml_root.find('expirationDays').text
consumeType = xml_root.find('consumeType').text
sellingAppeal = bool(xml_root.find('sellingAppeal').text)
for name in xml_root.findall("name"):
id = name.find("id").text
name = name.find("str").text
expirationDays = xml_root.find("expirationDays").text
consumeType = xml_root.find("consumeType").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:
self.logger.info(f"Inserted charge {id}")
@ -135,21 +164,23 @@ class ChuniReader(BaseReader):
for root, dirs, files in walk(avatar_dir):
for dir in dirs:
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()
strdata = bytedata.decode('UTF-8')
strdata = bytedata.decode("UTF-8")
xml_root = ET.fromstring(strdata)
for name in xml_root.findall('name'):
id = name.find('id').text
name = name.find('str').text
category = xml_root.find('category').text
for image in xml_root.findall('image'):
iconPath = image.find('path').text
for texture in xml_root.findall('texture'):
texturePath = texture.find('path').text
for name in xml_root.findall("name"):
id = name.find("id").text
name = name.find("str").text
category = xml_root.find("category").text
for image in xml_root.findall("image"):
iconPath = image.find("path").text
for texture in xml_root.findall("texture"):
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:
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.static import ChuniStaticData
__all__ = ["ChuniProfileData", "ChuniScoreData", "ChuniItemData", "ChuniStaticData"]
__all__ = ["ChuniProfileData", "ChuniScoreData", "ChuniItemData", "ChuniStaticData"]

View File

@ -13,7 +13,11 @@ character = Table(
"chuni_item_character",
metadata,
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("level", Integer),
Column("param1", Integer),
@ -26,27 +30,35 @@ character = Table(
Column("assignIllust", Integer),
Column("exMaxLv", Integer),
UniqueConstraint("user", "characterId", name="chuni_item_character_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
item = Table(
"chuni_item_item",
metadata,
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("itemKind", Integer),
Column("stock", Integer),
Column("isValid", Boolean),
UniqueConstraint("user", "itemId", "itemKind", name="chuni_item_item_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
duel = Table(
"chuni_item_duel",
metadata,
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("progress", Integer),
Column("point", Integer),
@ -57,14 +69,18 @@ duel = Table(
Column("param3", Integer),
Column("param4", Integer),
UniqueConstraint("user", "duelId", name="chuni_item_duel_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
map = Table(
"chuni_item_map",
metadata,
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("position", Integer),
Column("isClear", Boolean),
@ -72,17 +88,21 @@ map = Table(
Column("routeNumber", Integer),
Column("eventId", Integer),
Column("rate", Integer),
Column("statusCount", Integer),
Column("statusCount", Integer),
Column("isValid", Boolean),
UniqueConstraint("user", "mapId", name="chuni_item_map_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
map_area = Table(
"chuni_item_map_area",
metadata,
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("rate", Integer),
Column("isClear", Boolean),
@ -91,9 +111,10 @@ map_area = Table(
Column("statusCount", Integer),
Column("remainGridCount", Integer),
UniqueConstraint("user", "mapAreaId", name="chuni_item_map_area_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
class ChuniItemData(BaseData):
def put_character(self, user_id: int, character_data: Dict) -> Optional[int]:
character_data["user"] = user_id
@ -104,24 +125,26 @@ class ChuniItemData(BaseData):
conflict = sql.on_duplicate_key_update(**character_data)
result = self.execute(conflict)
if result is None: return None
if result is None:
return None
return result.lastrowid
def get_character(self, user_id: int, character_id: int) -> Optional[Dict]:
sql = select(character).where(and_(
character.c.user == user_id,
character.c.characterId == character_id
))
sql = select(character).where(
and_(character.c.user == user_id, character.c.characterId == character_id)
)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
return result.fetchone()
def get_characters(self, user_id: int) -> Optional[List[Row]]:
sql = select(character).where(character.c.user == user_id)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
return result.fetchall()
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)
result = self.execute(conflict)
if result is None: return None
if result is None:
return None
return result.lastrowid
def get_items(self, user_id: int, kind: int = None) -> Optional[List[Row]]:
if kind is None:
sql = select(item).where(item.c.user == user_id)
else:
sql = select(item).where(and_(
item.c.user == user_id,
item.c.itemKind == kind
))
sql = select(item).where(
and_(item.c.user == user_id, item.c.itemKind == kind)
)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
return result.fetchall()
def put_duel(self, user_id: int, duel_data: Dict) -> Optional[int]:
duel_data["user"] = user_id
@ -158,14 +182,16 @@ class ChuniItemData(BaseData):
conflict = sql.on_duplicate_key_update(**duel_data)
result = self.execute(conflict)
if result is None: return None
if result is None:
return None
return result.lastrowid
def get_duels(self, user_id: int) -> Optional[List[Row]]:
sql = select(duel).where(duel.c.user == user_id)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
return result.fetchall()
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)
result = self.execute(conflict)
if result is None: return None
if result is None:
return None
return result.lastrowid
def get_maps(self, user_id: int) -> Optional[List[Row]]:
sql = select(map).where(map.c.user == user_id)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
return result.fetchall()
def put_map_area(self, user_id: int, map_area_data: Dict) -> Optional[int]:
map_area_data["user"] = user_id
@ -196,12 +224,14 @@ class ChuniItemData(BaseData):
conflict = sql.on_duplicate_key_update(**map_area_data)
result = self.execute(conflict)
if result is None: return None
if result is None:
return None
return result.lastrowid
def get_map_areas(self, user_id: int) -> Optional[List[Row]]:
sql = select(map_area).where(map_area.c.user == user_id)
result = self.execute(sql)
if result is None: return None
return result.fetchall()
if result is None:
return None
return result.fetchall()

View File

@ -13,7 +13,11 @@ profile = Table(
"chuni_profile_data",
metadata,
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("exp", Integer),
Column("level", Integer),
@ -62,7 +66,7 @@ profile = Table(
Column("firstTutorialCancelNum", Integer),
Column("totalAdvancedHighScore", Integer),
Column("masterTutorialCancelNum", Integer),
Column("ext1", Integer), # Added in chunew
Column("ext1", Integer), # Added in chunew
Column("ext2", Integer),
Column("ext3", Integer),
Column("ext4", Integer),
@ -71,16 +75,20 @@ profile = Table(
Column("ext7", Integer),
Column("ext8", Integer),
Column("ext9", Integer),
Column("ext10", Integer),
Column("ext10", Integer),
Column("extStr1", String(255)),
Column("extStr2", String(255)),
Column("extLong1", Integer),
Column("extLong2", Integer),
Column("mapIconId", Integer),
Column("compatibleCmVersion", String(25)),
Column("medal", Integer),
Column("medal", 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("avatarFace", Integer, server_default="0"),
Column("eliteRankPoint", Integer, server_default="0"),
@ -121,14 +129,18 @@ profile = Table(
Column("netBattleEndState", Integer, server_default="0"),
Column("avatarHead", Integer, server_default="0"),
UniqueConstraint("user", "version", name="chuni_profile_profile_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
profile_ex = Table(
"chuni_profile_data_ex",
metadata,
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("ext1", Integer),
Column("ext2", Integer),
@ -165,14 +177,18 @@ profile_ex = Table(
Column("mapIconId", Integer),
Column("compatibleCmVersion", String(25)),
UniqueConstraint("user", "version", name="chuni_profile_data_ex_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
option = Table(
"chuni_profile_option",
metadata,
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("bgInfo", Integer),
Column("rating", Integer),
@ -195,7 +211,7 @@ option = Table(
Column("successSkill", Integer),
Column("successSlideHold", Integer),
Column("successTapTimbre", Integer),
Column("ext1", Integer), # Added in chunew
Column("ext1", Integer), # Added in chunew
Column("ext2", Integer),
Column("ext3", Integer),
Column("ext4", Integer),
@ -224,14 +240,18 @@ option = Table(
Column("playTimingOffset", Integer, server_default="0"),
Column("fieldWallPosition_120", Integer, server_default="0"),
UniqueConstraint("user", name="chuni_profile_option_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
option_ex = Table(
"chuni_profile_option_ex",
metadata,
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("ext2", Integer),
Column("ext3", Integer),
@ -253,51 +273,69 @@ option_ex = Table(
Column("ext19", Integer),
Column("ext20", Integer),
UniqueConstraint("user", name="chuni_profile_option_ex_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
recent_rating = Table(
"chuni_profile_recent_rating",
metadata,
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),
UniqueConstraint("user", name="chuni_profile_recent_rating_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
region = Table(
"chuni_profile_region",
metadata,
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("playCount", Integer),
UniqueConstraint("user", "regionId", name="chuni_profile_region_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
activity = Table(
"chuni_profile_activity",
metadata,
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("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("param1", Integer),
Column("param2", Integer),
Column("param3", Integer),
Column("param4", Integer),
UniqueConstraint("user", "kind", "activityId", name="chuni_profile_activity_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
charge = Table(
"chuni_profile_charge",
metadata,
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("stock", Integer),
Column("purchaseDate", String(25)),
@ -306,14 +344,18 @@ charge = Table(
Column("param2", Integer),
Column("paramDate", String(25)),
UniqueConstraint("user", "chargeId", name="chuni_profile_charge_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
emoney = Table(
"chuni_profile_emoney",
metadata,
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("ext2", Integer),
Column("ext3", Integer),
@ -321,20 +363,24 @@ emoney = Table(
Column("emoneyBrand", Integer),
Column("emoneyCredit", Integer),
UniqueConstraint("user", "emoneyBrand", name="chuni_profile_emoney_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
overpower = Table(
"chuni_profile_overpower",
metadata,
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("difficulty", Integer),
Column("rate", Integer),
Column("point", Integer),
UniqueConstraint("user", "genreId", "difficulty", name="chuni_profile_emoney_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
team = Table(
@ -343,18 +389,21 @@ team = Table(
Column("id", Integer, primary_key=True, nullable=False),
Column("teamName", String(255)),
Column("teamPoint", Integer),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
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["version"] = version
if "accessCode" in profile_data:
profile_data.pop("accessCode")
profile_data = self.fix_bools(profile_data)
sql = insert(profile).values(**profile_data)
conflict = sql.on_duplicate_key_update(**profile_data)
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}")
return None
return result.lastrowid
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(
and_(profile.c.user == aime_id, profile.c.version == version)
sql = (
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)
if result is None: return None
if result is None:
return None
return result.fetchone()
def get_profile_data(self, aime_id: int, version: int) -> Optional[Row]:
sql = select(profile).where(and_(
profile.c.user == aime_id,
profile.c.version == version,
))
sql = select(profile).where(
and_(
profile.c.user == aime_id,
profile.c.version == version,
)
)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
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["version"] = version
if "accessCode" in profile_ex_data:
profile_ex_data.pop("accessCode")
sql = insert(profile_ex).values(**profile_ex_data)
conflict = sql.on_duplicate_key_update(**profile_ex_data)
result = self.execute(conflict)
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 result.lastrowid
def get_profile_data_ex(self, aime_id: int, version: int) -> Optional[Row]:
sql = select(profile_ex).where(and_(
profile_ex.c.user == aime_id,
profile_ex.c.version == version,
))
sql = select(profile_ex).where(
and_(
profile_ex.c.user == aime_id,
profile_ex.c.version == version,
)
)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
return result.fetchone()
def put_profile_option(self, aime_id: int, option_data: Dict) -> Optional[int]:
option_data["user"] = aime_id
@ -416,7 +478,9 @@ class ChuniProfileData(BaseData):
result = self.execute(conflict)
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 result.lastrowid
@ -424,18 +488,23 @@ class ChuniProfileData(BaseData):
sql = select(option).where(option.c.user == aime_id)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
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
sql = insert(option_ex).values(**option_ex_data)
conflict = sql.on_duplicate_key_update(**option_ex_data)
result = self.execute(conflict)
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 result.lastrowid
@ -443,27 +512,32 @@ class ChuniProfileData(BaseData):
sql = select(option_ex).where(option_ex.c.user == aime_id)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
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(
user = aime_id,
recentRating = recent_rating_data
user=aime_id, 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)
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 result.lastrowid
def get_profile_recent_rating(self, aime_id: int) -> Optional[Row]:
sql = select(recent_rating).where(recent_rating.c.user == aime_id)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
return result.fetchone()
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["activityId"] = activity_data["id"]
activity_data.pop("id")
sql = insert(activity).values(**activity_data)
conflict = sql.on_duplicate_key_update(**activity_data)
result = self.execute(conflict)
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 result.lastrowid
def get_profile_activity(self, aime_id: int, kind: int) -> Optional[List[Row]]:
sql = select(activity).where(and_(
activity.c.user == aime_id,
activity.c.kind == kind
))
sql = select(activity).where(
and_(activity.c.user == aime_id, activity.c.kind == kind)
)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
return result.fetchall()
def put_profile_charge(self, aime_id: int, charge_data: Dict) -> Optional[int]:
charge_data["user"] = aime_id
sql = insert(charge).values(**charge_data)
conflict = sql.on_duplicate_key_update(**charge_data)
result = self.execute(conflict)
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 result.lastrowid
@ -507,9 +585,10 @@ class ChuniProfileData(BaseData):
sql = select(charge).where(charge.c.user == aime_id)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
return result.fetchall()
def add_profile_region(self, aime_id: int, region_id: int) -> Optional[int]:
pass
@ -523,29 +602,35 @@ class ChuniProfileData(BaseData):
conflict = sql.on_duplicate_key_update(**emoney_data)
result = self.execute(conflict)
if result is None: return None
if result is None:
return None
return result.lastrowid
def get_profile_emoney(self, aime_id: int) -> Optional[List[Row]]:
sql = select(emoney).where(emoney.c.user == aime_id)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
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
sql = insert(overpower).values(**overpower_data)
conflict = sql.on_duplicate_key_update(**overpower_data)
result = self.execute(conflict)
if result is None: return None
if result is None:
return None
return result.lastrowid
def get_profile_overpower(self, aime_id: int) -> Optional[List[Row]]:
sql = select(overpower).where(overpower.c.user == aime_id)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
return result.fetchall()

View File

@ -13,7 +13,11 @@ course = Table(
"chuni_score_course",
metadata,
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("classId", Integer),
Column("playCount", Integer),
@ -33,14 +37,18 @@ course = Table(
Column("orderId", Integer),
Column("playerRating", Integer),
UniqueConstraint("user", "courseId", name="chuni_score_course_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
best_score = Table(
"chuni_score_best",
metadata,
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("level", Integer),
Column("playCount", Integer),
@ -60,14 +68,18 @@ best_score = Table(
Column("ext1", Integer),
Column("theoryCount", Integer),
UniqueConstraint("user", "musicId", "level", name="chuni_score_best_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
playlog = Table(
"chuni_score_playlog",
metadata,
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("sortNumber", Integer),
Column("placeId", Integer),
@ -122,15 +134,17 @@ playlog = Table(
Column("charaIllustId", Integer),
Column("romVersion", String(255)),
Column("judgeHeaven", Integer),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
class ChuniScoreData(BaseData):
def get_courses(self, aime_id: int) -> Optional[Row]:
sql = select(course).where(course.c.user == aime_id)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
return result.fetchall()
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)
result = self.execute(conflict)
if result is None: return None
if result is None:
return None
return result.lastrowid
def get_scores(self, aime_id: int) -> Optional[Row]:
sql = select(best_score).where(best_score.c.user == aime_id)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
return result.fetchall()
def put_score(self, aime_id: int, score_data: Dict) -> Optional[int]:
score_data["user"] = aime_id
score_data = self.fix_bools(score_data)
@ -159,16 +175,18 @@ class ChuniScoreData(BaseData):
conflict = sql.on_duplicate_key_update(**score_data)
result = self.execute(conflict)
if result is None: return None
if result is None:
return None
return result.lastrowid
def get_playlogs(self, aime_id: int) -> Optional[Row]:
sql = select(playlog).where(playlog.c.user == aime_id)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
return result.fetchall()
def put_playlog(self, aime_id: int, playlog_data: Dict) -> Optional[int]:
playlog_data["user"] = aime_id
playlog_data = self.fix_bools(playlog_data)
@ -177,5 +195,6 @@ class ChuniScoreData(BaseData):
conflict = sql.on_duplicate_key_update(**playlog_data)
result = self.execute(conflict)
if result is None: return None
if result is None:
return None
return result.lastrowid

View File

@ -19,7 +19,7 @@ events = Table(
Column("name", String(255)),
Column("enabled", Boolean, server_default="1"),
UniqueConstraint("version", "eventId", name="chuni_static_events_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
music = Table(
@ -30,13 +30,13 @@ music = Table(
Column("songId", Integer),
Column("chartId", Integer),
Column("title", String(255)),
Column("artist", String(255)),
Column("artist", String(255)),
Column("level", Float),
Column("genre", String(255)),
Column("jacketPath", String(255)),
Column("worldsEndTag", String(7)),
UniqueConstraint("version", "songId", "chartId", name="chuni_static_music_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
charge = Table(
@ -51,7 +51,7 @@ charge = Table(
Column("sellingAppeal", Boolean),
Column("enabled", Boolean, server_default="1"),
UniqueConstraint("version", "chargeId", name="chuni_static_charge_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
avatar = Table(
@ -65,159 +65,203 @@ avatar = Table(
Column("iconPath", String(255)),
Column("texturePath", String(255)),
UniqueConstraint("version", "avatarAccessoryId", name="chuni_static_avatar_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
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(
version = version,
eventId = event_id,
type = type,
name = name
version=version, eventId=event_id, type=type, name=name
)
conflict = sql.on_duplicate_key_update(
name = name
)
conflict = sql.on_duplicate_key_update(name=name)
result = self.execute(conflict)
if result is None: return None
if result is None:
return None
return result.lastrowid
def update_event(self, version: int, event_id: int, enabled: bool) -> Optional[bool]:
sql = events.update(and_(events.c.version == version, events.c.eventId == event_id)).values(
enabled = enabled
)
def update_event(
self, version: int, event_id: int, enabled: bool
) -> Optional[bool]:
sql = events.update(
and_(events.c.version == version, events.c.eventId == event_id)
).values(enabled=enabled)
result = self.execute(sql)
if result is None:
self.logger.warn(f"update_event: failed to update event! version: {version}, event_id: {event_id}, enabled: {enabled}")
if result is None:
self.logger.warn(
f"update_event: failed to update event! version: {version}, event_id: {event_id}, enabled: {enabled}"
)
return None
event = self.get_event(version, event_id)
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 event["enabled"]
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)
if result is None: return None
if result is None:
return None
return result.fetchone()
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)
if result is None: return None
if result is None:
return None
return result.fetchall()
def get_events(self, version: int) -> Optional[List[Row]]:
sql = select(events).where(events.c.version == version)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
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(
version = version,
songId = song_id,
chartId = chart_id,
title = title,
artist = artist,
level = level,
genre = genre,
jacketPath = jacketPath,
worldsEndTag = we_tag,
version=version,
songId=song_id,
chartId=chart_id,
title=title,
artist=artist,
level=level,
genre=genre,
jacketPath=jacketPath,
worldsEndTag=we_tag,
)
conflict = sql.on_duplicate_key_update(
title = title,
artist = artist,
level = level,
genre = genre,
jacketPath = jacketPath,
worldsEndTag = we_tag,
title=title,
artist=artist,
level=level,
genre=genre,
jacketPath=jacketPath,
worldsEndTag=we_tag,
)
result = self.execute(conflict)
if result is None: return None
if result is None:
return None
return result.lastrowid
def put_charge(self, version: int, charge_id: int, name: str, expiration_days: int,
consume_type: int, selling_appeal: bool) -> Optional[int]:
def put_charge(
self,
version: int,
charge_id: int,
name: str,
expiration_days: int,
consume_type: int,
selling_appeal: bool,
) -> Optional[int]:
sql = insert(charge).values(
version = version,
chargeId = charge_id,
name = name,
expirationDays = expiration_days,
consumeType = consume_type,
sellingAppeal = selling_appeal,
version=version,
chargeId=charge_id,
name=name,
expirationDays=expiration_days,
consumeType=consume_type,
sellingAppeal=selling_appeal,
)
conflict = sql.on_duplicate_key_update(
name = name,
expirationDays = expiration_days,
consumeType = consume_type,
sellingAppeal = selling_appeal,
name=name,
expirationDays=expiration_days,
consumeType=consume_type,
sellingAppeal=selling_appeal,
)
result = self.execute(conflict)
if result is None: return None
if result is None:
return None
return result.lastrowid
def get_enabled_charges(self, version: int) -> Optional[List[Row]]:
sql = select(charge).where(and_(
charge.c.version == version,
charge.c.enabled == True
))
sql = select(charge).where(
and_(charge.c.version == version, charge.c.enabled == True)
)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
return result.fetchall()
def get_charges(self, version: int) -> Optional[List[Row]]:
sql = select(charge).where(charge.c.version == version)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
return result.fetchall()
def get_music_chart(self, version: int, song_id: int, chart_id: int) -> Optional[List[Row]]:
sql = select(music).where(and_(
music.c.version == version,
music.c.songId == song_id,
music.c.chartId == chart_id
))
def get_music_chart(
self, version: int, song_id: int, chart_id: int
) -> Optional[List[Row]]:
sql = select(music).where(
and_(
music.c.version == version,
music.c.songId == song_id,
music.c.chartId == chart_id,
)
)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
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(
version = version,
avatarAccessoryId = avatarAccessoryId,
name = name,
category = category,
iconPath = iconPath,
texturePath = texturePath,
version=version,
avatarAccessoryId=avatarAccessoryId,
name=name,
category=category,
iconPath=iconPath,
texturePath=texturePath,
)
conflict = sql.on_duplicate_key_update(
name = name,
category = category,
iconPath = iconPath,
texturePath = texturePath,
name=name,
category=category,
iconPath=iconPath,
texturePath=texturePath,
)
result = self.execute(conflict)
if result is None: return None
if result is None:
return None
return result.lastrowid

View File

@ -5,12 +5,13 @@ from titles.chuni.base import ChuniBase
from titles.chuni.const import ChuniConstants
from titles.chuni.config import ChuniConfig
class ChuniStar(ChuniBase):
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_STAR
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"
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.config import ChuniConfig
class ChuniStarPlus(ChuniBase):
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_STAR_PLUS
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"
return ret
return ret

View File

@ -10,12 +10,14 @@ from titles.cm.const import CardMakerConstants
from titles.cm.config import CardMakerConfig
class CardMakerBase():
class CardMakerBase:
def __init__(self, core_cfg: CoreConfig, game_cfg: CardMakerConfig) -> None:
self.core_cfg = core_cfg
self.game_cfg = game_cfg
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.logger = logging.getLogger("cardmaker")
self.game = CardMakerConstants.GAME_CODE
@ -31,27 +33,19 @@ class CardMakerBase():
return {
"length": 3,
"gameConnectList": [
{
"modelKind": 0,
"type": 1,
"titleUri": f"{uri}/SDHD/200/"
},
{
"modelKind": 1,
"type": 1,
"titleUri": f"{uri}/SDEZ/120/"
},
{
"modelKind": 2,
"type": 1,
"titleUri": f"{uri}/SDDT/130/"
}
]
{"modelKind": 0, "type": 1, "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:
reboot_start = date.strftime(datetime.now() + timedelta(hours=3), self.date_time_format)
reboot_end = date.strftime(datetime.now() + timedelta(hours=4), self.date_time_format)
reboot_start = date.strftime(
datetime.now() + timedelta(hours=3), self.date_time_format
)
reboot_end = date.strftime(
datetime.now() + timedelta(hours=4), self.date_time_format
)
return {
"gameSetting": {
@ -67,18 +61,14 @@ class CardMakerBase():
"maxCountCard": 100,
"watermark": False,
"isMaintenance": False,
"isBackgroundDistribute": False
"isBackgroundDistribute": False,
},
"isDumpUpload": False,
"isAou": False
"isAou": False,
}
def handle_get_client_bookkeeping_api_request(self, data: Dict) -> Dict:
return {
"placeId": data["placeId"],
"length": 0,
"clientBookkeepingList": []
}
return {"placeId": data["placeId"], "length": 0, "clientBookkeepingList": []}
def handle_upsert_client_setting_api_request(self, data: Dict) -> Dict:
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}"
else:
uri = f"http://{self.core_cfg.title.hostname}"
ret["gameConnectList"][0]["titleUri"] = f"{uri}/SDHD/205/"
ret["gameConnectList"][1]["titleUri"] = f"{uri}/SDEZ/125/"
ret["gameConnectList"][2]["titleUri"] = f"{uri}/SDDT/135/"

View File

@ -1,17 +1,23 @@
from core.config import CoreConfig
class CardMakerServerConfig():
class CardMakerServerConfig:
def __init__(self, parent_config: "CardMakerConfig") -> None:
self.__config = parent_config
@property
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
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):

View File

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

View File

@ -18,23 +18,29 @@ from titles.cm.base import CardMakerBase
from titles.cm.cm136 import CardMaker136
class CardMakerServlet():
class CardMakerServlet:
def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None:
self.core_cfg = core_cfg
self.game_cfg = CardMakerConfig()
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 = [
CardMakerBase(core_cfg, self.game_cfg),
CardMaker136(core_cfg, self.game_cfg)
CardMaker136(core_cfg, self.game_cfg),
]
self.logger = logging.getLogger("cardmaker")
log_fmt_str = "[%(asctime)s] Card Maker | %(levelname)s | %(message)s"
log_fmt = logging.Formatter(log_fmt_str)
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.core_cfg.server.log_dir, "cardmaker"), encoding='utf8',
when="d", backupCount=10)
fileHandler = TimedRotatingFileHandler(
"{0}/{1}.log".format(self.core_cfg.server.log_dir, "cardmaker"),
encoding="utf8",
when="d",
backupCount=10,
)
fileHandler.setFormatter(log_fmt)
@ -45,20 +51,29 @@ class CardMakerServlet():
self.logger.addHandler(consoleHandler)
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
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()
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:
return (False, "", "")
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/", "")
@ -86,7 +101,8 @@ class CardMakerServlet():
except zlib.error as e:
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"}')
req_data = json.loads(unzip)
@ -104,12 +120,11 @@ class CardMakerServlet():
resp = handler(req_data)
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"}')
if resp is None:
resp = {'returnCode': 1}
resp = {"returnCode": 1}
self.logger.info(f"Response {resp}")

View File

@ -15,14 +15,21 @@ from titles.ongeki.config import OngekiConfig
class CardMakerReader(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)
self.ongeki_data = OngekiData(config)
try:
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:
self.logger.error(f"Invalid Card Maker version {version}")
exit(1)
@ -30,7 +37,7 @@ class CardMakerReader(BaseReader):
def read(self) -> None:
static_datas = {
"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 = []
@ -41,7 +48,9 @@ class CardMakerReader(BaseReader):
read_csv = getattr(CardMakerReader, func)
read_csv(self, f"{self.bin_dir}/MU3/{file}")
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:
data_dirs += self.get_data_directories(self.opt_dir)
@ -64,7 +73,7 @@ class CardMakerReader(BaseReader):
row["kind"],
type=row["type"],
isCeiling=True if row["isCeiling"] == "1" else False,
maxSelectPoint=row["maxSelectPoint"]
maxSelectPoint=row["maxSelectPoint"],
)
self.logger.info(f"Added gacha {row['gachaId']}")
@ -81,7 +90,7 @@ class CardMakerReader(BaseReader):
rarity=row["rarity"],
weight=row["weight"],
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")
@ -95,7 +104,7 @@ class CardMakerReader(BaseReader):
"Pickup": "Pickup",
"RecoverFiveShotFlag": "BonusRestored",
"Free": "Free",
"FreeSR": "Free"
"FreeSR": "Free",
}
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:
troot = ET.fromstring(f.read())
name = troot.find('Name').find('str').text
gacha_id = int(troot.find('Name').find('id').text)
name = troot.find("Name").find("str").text
gacha_id = int(troot.find("Name").find("id").text)
# skip already existing gachas
if self.ongeki_data.static.get_gacha(
OngekiConstants.VER_ONGEKI_BRIGHT_MEMORY, gacha_id) is not None:
self.logger.info(f"Gacha {gacha_id} already added, skipping")
if (
self.ongeki_data.static.get_gacha(
OngekiConstants.VER_ONGEKI_BRIGHT_MEMORY, gacha_id
)
is not None
):
self.logger.info(
f"Gacha {gacha_id} already added, skipping"
)
continue
# 1140 is the first bright memory gacha
@ -120,7 +135,8 @@ class CardMakerReader(BaseReader):
version = OngekiConstants.VER_ONGEKI_BRIGHT_MEMORY
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
is_ceiling, max_select_point = 0, 0
@ -134,5 +150,6 @@ class CardMakerReader(BaseReader):
name,
gacha_kind,
isCeiling=is_ceiling,
maxSelectPoint=max_select_point)
maxSelectPoint=max_select_point,
)
self.logger.info(f"Added gacha {gacha_id}")

View File

@ -7,4 +7,4 @@ index = CxbServlet
database = CxbData
reader = CxbReader
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.database import CxbData
class CxbBase():
class CxbBase:
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.data = CxbData(cfg) # Database
self.data = CxbData(cfg) # Database
self.game = CxbConstants.GAME_CODE
self.logger = logging.getLogger("cxb")
self.version = CxbConstants.VER_CROSSBEATS_REV
def handle_action_rpreq_request(self, data: Dict) -> Dict:
return({})
return {}
def handle_action_hitreq_request(self, data: Dict) -> Dict:
return({"data":[]})
return {"data": []}
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:
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']}")
return({"exist": "false", "logout": "true"})
return {"exist": "false", "logout": "true"}
def handle_auth_entry_request(self, data: Dict) -> Dict:
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:
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:
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")
return({})
return {}
def handle_action_loadrange_request(self, data: Dict) -> Dict:
range_start = data['loadrange']['range'][0]
range_end = data['loadrange']['range'][1]
uid = data['loadrange']['uid']
range_start = data["loadrange"]["range"][0]
range_end = data["loadrange"]["range"][1]
uid = data["loadrange"]["uid"]
self.logger.info(f"Load data for {uid}")
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 = []
index = []
versionindex = []
for profile_index in profile:
profile_data = profile_index["data"]
if int(range_start) == 800000:
return({"index":range_start, "data":[], "version":10400})
if not ( int(range_start) <= int(profile_index[3]) <= int(range_end) ):
return {"index": range_start, "data": [], "version": 10400}
if not (int(range_start) <= int(profile_index[3]) <= int(range_end)):
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:
continue
#Prevent loading of songs saved in the profile
# Prevent loading of songs saved in the profile
elif 100000 <= int(profile_index[3]) <= 110000:
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:
continue
#Prevent loading of stories in the profile
# Prevent loading of stories in the profile
elif 900000 <= int(profile_index[3]) <= 900200:
continue
else:
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
200000 = Shop
300000 = Courses
@ -96,101 +105,140 @@ class CxbBase():
700000 = rcLog
800000 = Partners
900000 = Stories
'''
"""
# Coupons
for i in range(500,510):
for i in range(500, 510):
index.append(str(i))
couponid = int(i) - 500
dataValue = [{
"couponId":str(couponid),
"couponNum":"1",
"couponLog":[],
}]
data1.append(b64encode(bytes(json.dumps(dataValue[0], separators=(',', ':')), 'utf-8')).decode('utf-8'))
dataValue = [
{
"couponId": str(couponid),
"couponNum": "1",
"couponLog": [],
}
]
data1.append(
b64encode(
bytes(json.dumps(dataValue[0], separators=(",", ":")), "utf-8")
).decode("utf-8")
)
# ShopList_Title
for i in range(200000,201451):
for i in range(200000, 201451):
index.append(str(i))
shopid = int(i) - 200000
dataValue = [{
"shopId":shopid,
"shopState":"2",
"isDisable":"t",
"isDeleted":"f",
"isSpecialFlag":"f"
}]
data1.append(b64encode(bytes(json.dumps(dataValue[0], separators=(',', ':')), 'utf-8')).decode('utf-8'))
dataValue = [
{
"shopId": shopid,
"shopState": "2",
"isDisable": "t",
"isDeleted": "f",
"isSpecialFlag": "f",
}
]
data1.append(
b64encode(
bytes(json.dumps(dataValue[0], separators=(",", ":")), "utf-8")
).decode("utf-8")
)
#ShopList_Icon
for i in range(202000,202264):
# ShopList_Icon
for i in range(202000, 202264):
index.append(str(i))
shopid = int(i) - 200000
dataValue = [{
"shopId":shopid,
"shopState":"2",
"isDisable":"t",
"isDeleted":"f",
"isSpecialFlag":"f"
}]
data1.append(b64encode(bytes(json.dumps(dataValue[0], separators=(',', ':')), 'utf-8')).decode('utf-8'))
dataValue = [
{
"shopId": shopid,
"shopState": "2",
"isDisable": "t",
"isDeleted": "f",
"isSpecialFlag": "f",
}
]
data1.append(
b64encode(
bytes(json.dumps(dataValue[0], separators=(",", ":")), "utf-8")
).decode("utf-8")
)
#Stories
for i in range(900000,900003):
# Stories
for i in range(900000, 900003):
index.append(str(i))
storyid = int(i) - 900000
dataValue = [{
"storyId":storyid,
"unlockState1":["t"] * 10,
"unlockState2":["t"] * 10,
"unlockState3":["t"] * 10,
"unlockState4":["t"] * 10,
"unlockState5":["t"] * 10,
"unlockState6":["t"] * 10,
"unlockState7":["t"] * 10,
"unlockState8":["t"] * 10,
"unlockState9":["t"] * 10,
"unlockState10":["t"] * 10,
"unlockState11":["t"] * 10,
"unlockState12":["t"] * 10,
"unlockState13":["t"] * 10,
"unlockState14":["t"] * 10,
"unlockState15":["t"] * 10,
"unlockState16":["t"] * 10
}]
data1.append(b64encode(bytes(json.dumps(dataValue[0], separators=(',', ':')), 'utf-8')).decode('utf-8'))
dataValue = [
{
"storyId": storyid,
"unlockState1": ["t"] * 10,
"unlockState2": ["t"] * 10,
"unlockState3": ["t"] * 10,
"unlockState4": ["t"] * 10,
"unlockState5": ["t"] * 10,
"unlockState6": ["t"] * 10,
"unlockState7": ["t"] * 10,
"unlockState8": ["t"] * 10,
"unlockState9": ["t"] * 10,
"unlockState10": ["t"] * 10,
"unlockState11": ["t"] * 10,
"unlockState12": ["t"] * 10,
"unlockState13": ["t"] * 10,
"unlockState14": ["t"] * 10,
"unlockState15": ["t"] * 10,
"unlockState16": ["t"] * 10,
}
]
data1.append(
b64encode(
bytes(json.dumps(dataValue[0], separators=(",", ":")), "utf-8")
).decode("utf-8")
)
for song in songs:
song_data = song["data"]
songCode = []
songCode.append({
"mcode": song_data['mcode'],
"musicState": song_data['musicState'],
"playCount": song_data['playCount'],
"totalScore": song_data['totalScore'],
"highScore": song_data['highScore'],
"everHighScore": song_data['everHighScore'] if 'everHighScore' in song_data else ["0","0","0","0","0"],
"clearRate": song_data['clearRate'],
"rankPoint": song_data['rankPoint'],
"normalCR": song_data['normalCR'] if 'normalCR' in song_data else ["0","0","0","0","0"],
"survivalCR": song_data['survivalCR'] if 'survivalCR' in song_data else ["0","0","0","0","0"],
"ultimateCR": song_data['ultimateCR'] if 'ultimateCR' in song_data else ["0","0","0","0","0"],
"nohopeCR": song_data['nohopeCR'] if 'nohopeCR' in song_data else ["0","0","0","0","0"],
"combo": song_data['combo'],
"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'))
songCode.append(
{
"mcode": song_data["mcode"],
"musicState": song_data["musicState"],
"playCount": song_data["playCount"],
"totalScore": song_data["totalScore"],
"highScore": song_data["highScore"],
"everHighScore": song_data["everHighScore"]
if "everHighScore" in song_data
else ["0", "0", "0", "0", "0"],
"clearRate": song_data["clearRate"],
"rankPoint": song_data["rankPoint"],
"normalCR": song_data["normalCR"]
if "normalCR" in song_data
else ["0", "0", "0", "0", "0"],
"survivalCR": song_data["survivalCR"]
if "survivalCR" in song_data
else ["0", "0", "0", "0", "0"],
"ultimateCR": song_data["ultimateCR"]
if "ultimateCR" in song_data
else ["0", "0", "0", "0", "0"],
"nohopeCR": song_data["nohopeCR"]
if "nohopeCR" in song_data
else ["0", "0", "0", "0", "0"],
"combo": song_data["combo"],
"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:
try:
@ -198,66 +246,81 @@ class CxbBase():
v_profile_data = v_profile["data"]
versionindex.append(int(v_profile_data["appVersion"]))
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:
save_data = data['saveindex']
save_data = data["saveindex"]
try:
#REV Omnimix Version Fetcher
gameversion = data['saveindex']['data'][0][2]
# REV Omnimix Version Fetcher
gameversion = data["saveindex"]["data"][0][2]
self.logger.warning(f"Game Version is {gameversion}")
except:
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:
profileIndex = save_data['index'].index('0')
profileIndex = save_data["index"].index("0")
except:
return({"data":""}) #Maybe
return {"data": ""} # Maybe
profile = json.loads(save_data["data"][profileIndex])
aimeId = profile["aimeId"]
@ -265,65 +328,91 @@ class CxbBase():
for index, value in enumerate(data["saveindex"]["data"]):
if int(data["saveindex"]["index"][index]) == 101:
self.data.profile.put_profile(aimeId, self.version, data["saveindex"]["index"][index], value)
if int(data["saveindex"]["index"][index]) >= 700000 and int(data["saveindex"]["index"][index])<= 701000:
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:
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:
self.data.profile.put_profile(
aimeId, self.version, data["saveindex"]["index"][index], value
)
if (
int(data["saveindex"]["index"][index]) >= 700000
and int(data["saveindex"]["index"][index]) <= 701000
):
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
):
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
# MusicList Index for the profile
indexSongList = []
for value in data["saveindex"]["index"]:
if int(value) in range(100000,110000):
if int(value) in range(100000, 110000):
indexSongList.append(value)
for index, value in enumerate(data["saveindex"]["data"]):
if 'mcode' not in value:
if "mcode" not in value:
continue
if 'playedUserId' in value:
if "playedUserId" in value:
continue
data1 = json.loads(value)
songCode = []
songCode.append({
"mcode": data1['mcode'],
"musicState": data1['musicState'],
"playCount": data1['playCount'],
"totalScore": data1['totalScore'],
"highScore": data1['highScore'],
"everHighScore": data1['everHighScore'],
"clearRate": data1['clearRate'],
"rankPoint": data1['rankPoint'],
"normalCR": data1['normalCR'],
"survivalCR": data1['survivalCR'],
"ultimateCR": data1['ultimateCR'],
"nohopeCR": data1['nohopeCR'],
"combo": data1['combo'],
"coupleUserId": data1['coupleUserId'],
"difficulty": data1['difficulty'],
"isFullCombo": data1['isFullCombo'],
"clearGaugeType": data1['clearGaugeType'],
"fieldType": data1['fieldType'],
"gameType": data1['gameType'],
"grade": data1['grade'],
"unlockState": data1['unlockState'],
"extraState": data1['extraState'],
"index": indexSongList[i]
})
songCode.append(
{
"mcode": data1["mcode"],
"musicState": data1["musicState"],
"playCount": data1["playCount"],
"totalScore": data1["totalScore"],
"highScore": data1["highScore"],
"everHighScore": data1["everHighScore"],
"clearRate": data1["clearRate"],
"rankPoint": data1["rankPoint"],
"normalCR": data1["normalCR"],
"survivalCR": data1["survivalCR"],
"ultimateCR": data1["ultimateCR"],
"nohopeCR": data1["nohopeCR"],
"combo": data1["combo"],
"coupleUserId": data1["coupleUserId"],
"difficulty": data1["difficulty"],
"isFullCombo": data1["isFullCombo"],
"clearGaugeType": data1["clearGaugeType"],
"fieldType": data1["fieldType"],
"gameType": data1["gameType"],
"grade": data1["grade"],
"unlockState": data1["unlockState"],
"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
return({})
return {}
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}")
p = self.data.score.get_best_rankings(uid)
@ -331,90 +420,122 @@ class CxbBase():
for rank in p:
if rank["song_id"] is not None:
rankList.append({
"sc": [rank["score"],rank["song_id"]],
"rid": rank["rev_id"],
"clear": rank["clear"]
})
rankList.append(
{
"sc": [rank["score"], rank["song_id"]],
"rid": rank["rev_id"],
"clear": rank["clear"],
}
)
else:
rankList.append({
"sc": [rank["score"]],
"rid": rank["rev_id"],
"clear": rank["clear"]
})
rankList.append(
{
"sc": [rank["score"]],
"rid": rank["rev_id"],
"clear": rank["clear"],
}
)
return({
return {
"uid": data["sprankreq"]["uid"],
"aid": data["sprankreq"]["aid"],
"rank": rankList,
"rankx":[1,1,1]
})
"rankx": [1, 1, 1],
}
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:
return({"msgs":[]})
return {"msgs": []}
def handle_auth_logout_request(self, data: Dict) -> Dict:
return({"auth":True})
def handle_action_rankreg_request(self, data: Dict) -> Dict:
uid = data['rankreg']['uid']
return {"auth": True}
def handle_action_rankreg_request(self, data: Dict) -> Dict:
uid = data["rankreg"]["uid"]
self.logger.info(f"Put {len(data['rankreg']['data'])} rankings for {uid}")
for rid in data['rankreg']['data']:
#REV S2
for rid in data["rankreg"]["data"]:
# REV S2
if "clear" in rid:
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:
self.data.score.put_ranking(user_id=uid, rev_id=int(rid["rid"]), song_id=0, score=int(rid["sc"][0]), clear=rid["clear"])
#REV
self.data.score.put_ranking(
user_id=uid,
rev_id=int(rid["rid"]),
song_id=0,
score=int(rid["sc"][0]),
clear=rid["clear"],
)
# REV
else:
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:
self.data.score.put_ranking(user_id=uid, rev_id=int(rid["rid"]), song_id=0, score=int(rid["sc"][0]), clear=0)
return({})
self.data.score.put_ranking(
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:
uid = data['addenergy']['uid']
uid = data["addenergy"]["uid"]
self.logger.info(f"Add energy to user {uid}")
profile = self.data.profile.get_profile_index(0, uid, self.version)
data1 = profile["data"]
p = self.data.item.get_energy(uid)
energy = p["energy"]
if not p:
if not p:
self.data.item.put_energy(uid, 5)
return({
return {
"class": data1["myClass"],
"granted": "5",
"total": "5",
"threshold": "1000"
})
"threshold": "1000",
}
array = []
newenergy = int(energy) + 5
self.data.item.put_energy(uid, newenergy)
if int(energy) <= 995:
array.append({
"class": data1["myClass"],
"granted": "5",
"total": str(energy),
"threshold": "1000"
})
array.append(
{
"class": data1["myClass"],
"granted": "5",
"total": str(energy),
"threshold": "1000",
}
)
else:
array.append({
"class": data1["myClass"],
"granted": "0",
"total": str(energy),
"threshold": "1000"
})
array.append(
{
"class": data1["myClass"],
"granted": "0",
"total": str(energy),
"threshold": "1000",
}
)
return array[0]
def handle_action_eventreq_request(self, data: Dict) -> Dict:

View File

@ -1,40 +1,60 @@
from core.config import CoreConfig
class CxbServerConfig():
class CxbServerConfig:
def __init__(self, parent_config: "CxbConfig"):
self.__config = parent_config
@property
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
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
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
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
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
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
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
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):
def __init__(self) -> None:

View File

@ -1,4 +1,4 @@
class CxbConstants():
class CxbConstants:
GAME_CODE = "SDCA"
CONFIG_NAME = "cxb.yaml"
@ -8,8 +8,13 @@ class CxbConstants():
VER_CROSSBEATS_REV_SUNRISE_S2 = 2
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
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.config import CoreConfig
from titles.cxb.schema import CxbProfileData, CxbScoreData, CxbItemData, CxbStaticData
class CxbData(Data):
def __init__(self, cfg: CoreConfig) -> None:
super().__init__(cfg)

View File

@ -17,6 +17,7 @@ from titles.cxb.rev import CxbRev
from titles.cxb.rss1 import CxbRevSunriseS1
from titles.cxb.rss2 import CxbRevSunriseS2
class CxbServlet(resource.Resource):
def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None:
self.isLeaf = True
@ -24,62 +25,85 @@ class CxbServlet(resource.Resource):
self.core_cfg = core_cfg
self.game_cfg = CxbConfig()
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")
if not hasattr(self.logger, "inited"):
log_fmt_str = "[%(asctime)s] CXB | %(levelname)s | %(message)s"
log_fmt = logging.Formatter(log_fmt_str)
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.core_cfg.server.log_dir, "cxb"), encoding='utf8',
when="d", backupCount=10)
fileHandler = TimedRotatingFileHandler(
"{0}/{1}.log".format(self.core_cfg.server.log_dir, "cxb"),
encoding="utf8",
when="d",
backupCount=10,
)
fileHandler.setFormatter(log_fmt)
consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt)
self.logger.addHandler(fileHandler)
self.logger.addHandler(consoleHandler)
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.versions = [
CxbRev(core_cfg, self.game_cfg),
CxbRevSunriseS1(core_cfg, self.game_cfg),
CxbRevSunriseS2(core_cfg, self.game_cfg),
]
@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()
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:
return (False, "", "")
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/", "")
def setup(self):
if self.game_cfg.server.enable:
endpoints.serverFromString(reactor, f"tcp:{self.game_cfg.server.port}:interface={self.core_cfg.server.listen_address}")\
.listen(server.Site(CxbServlet(self.core_cfg, self.cfg_dir)))
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)))
endpoints.serverFromString(
reactor,
f"tcp:{self.game_cfg.server.port}:interface={self.core_cfg.server.listen_address}",
).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:
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):
version = 0
@ -96,21 +120,28 @@ class CxbServlet(resource.Resource):
except Exception as e:
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:
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""
if req_json == {}:
self.logger.warn(f"Empty json request to {req_url}")
return b""
cmd = url_split[len(url_split) - 1]
subcmd = list(req_json.keys())[0]
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}")
return b""
@ -119,7 +150,9 @@ class CxbServlet(resource.Resource):
version = int(filetype_split[0])
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:
subcmd = f"{inflection.underscore(match.group(1))}xxxx"
else:
@ -128,7 +161,7 @@ class CxbServlet(resource.Resource):
filetype = subcmd
func_to_find = f"handle_{cmd}_{subcmd}_request"
if version <= 10102:
version_string = "Rev"
internal_ver = CxbConstants.VER_CROSSBEATS_REV
@ -136,28 +169,28 @@ class CxbServlet(resource.Resource):
elif version == 10113 or version == 10103:
version_string = "Rev SunriseS1"
internal_ver = CxbConstants.VER_CROSSBEATS_REV_SUNRISE_S1
elif version >= 10114 or version == 10104:
version_string = "Rev SunriseS2"
internal_ver = CxbConstants.VER_CROSSBEATS_REV_SUNRISE_S2
else:
version_string = "Base"
self.logger.info(f"{version_string} Request {req_url} -> {filetype}")
self.logger.debug(req_json)
try:
handler = getattr(self.versions[internal_ver], func_to_find)
resp = handler(req_json)
except AttributeError as e:
except AttributeError as e:
self.logger.warning(f"Unhandled {version_string} request {req_url} - {e}")
resp = {}
except Exception as e:
self.logger.error(f"Error handling {version_string} method {req_url} - {e}")
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")

View File

@ -8,13 +8,23 @@ from core.config import CoreConfig
from titles.cxb.database import CxbData
from titles.cxb.const import CxbConstants
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)
self.data = CxbData(config)
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:
self.logger.error(f"Invalid project cxb version {version}")
exit(1)
@ -28,7 +38,7 @@ class CxbReader(BaseReader):
if pull_bin_ram:
self.read_csv(f"{self.bin_dir}")
def read_csv(self, bin_dir: str) -> None:
self.logger.info(f"Read csv from {bin_dir}")
@ -45,18 +55,73 @@ class CxbReader(BaseReader):
if not "N/A" in row["standard"]:
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"]:
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"]:
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"]:
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"]:
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:
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.const import CxbConstants
class CxbRev(CxbBase):
def __init__(self, cfg: CoreConfig, game_cfg: CxbConfig) -> None:
super().__init__(cfg, game_cfg)
self.version = CxbConstants.VER_CROSSBEATS_REV
def handle_data_path_list_request(self, data: Dict) -> Dict:
return { "data": "" }
return {"data": ""}
def handle_data_putlog_request(self, data: Dict) -> Dict:
if data["putlog"]["type"] == "ResultLog":
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"])
return({"data":True})
return {"data": True }
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"],
)
return {"data": True}
return {"data": True}
@cached(lifetime=86400)
@cached(lifetime=86400)
def handle_data_music_list_request(self, data: Dict) -> Dict:
ret_str = ""
with open(r"titles/cxb/rev_data/MusicArchiveList.csv") as music:
lines = music.readlines()
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"
return({"data":ret_str})
return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_item_list_icon_request(self, data: Dict) -> Dict:
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()
for line in lines:
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:
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()
for line in lines:
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:
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()
for line in lines:
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:
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()
for line in lines:
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:
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()
for line in lines:
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:
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()
for line in lines:
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:
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()
for line in lines:
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:
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()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
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":""})
return {"data": ret_str}
@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:
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()
for line in lines:
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:
return({"data":""})
return {"data": ""}
@cached(lifetime=86400)
@cached(lifetime=86400)
def handle_data_exxxxx_request(self, data: Dict) -> Dict:
extra_num = int(data["dldate"]["filetype"][-4:])
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()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
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": ""})
return {"data": ret_str}
@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:
ret_str = ""
with open(r"titles/cxb/rev_data/NewsList.csv", encoding="UTF-8") as news:
lines = news.readlines()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str})
return {"data": ret_str}
def handle_data_tips_request(self, data: Dict) -> Dict:
return({"data":""})
return {"data": ""}
@cached(lifetime=86400)
def handle_data_license_request(self, data: Dict) -> Dict:
ret_str = ""
@ -167,90 +203,104 @@ class CxbRev(CxbBase):
lines = lic.readlines()
for line in lines:
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:
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()
for line in lines:
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:
# Removed the CSVs since the format isnt quite right
extra_num = int(data["dldate"]["filetype"][-4:])
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()
for line in lines:
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:
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()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
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": ""})
return {"data": ret_str}
@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:
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()
for line in lines:
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:
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": ""})
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:
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()
for line in lines:
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:
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:
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.const import CxbConstants
class CxbRevSunriseS1(CxbBase):
def __init__(self, cfg: CoreConfig, game_cfg: CxbConfig) -> None:
super().__init__(cfg, game_cfg)
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:
ret_str = ""
with open(r"titles/cxb/rss1_data/MusicArchiveList.csv") as music:
lines = music.readlines()
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"
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:
#ItemListIcon load
# ItemListIcon load
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()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
#ItemListTitle load
# ItemListTitle load
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()
for line in lines:
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:
#ShopListIcon load
# ShopListIcon load
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()
for line in lines:
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:
ret_str = ""
with open(r"titles/cxb/rss1_data/NewsList.csv", encoding="UTF-8") as news:
lines = news.readlines()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str})
return {"data": ret_str}
def handle_data_tips_request(self, data: Dict) -> Dict:
return({"data":""})
return {"data": ""}
def handle_data_release_info_list_request(self, data: Dict) -> Dict:
return({"data":""})
return {"data": ""}
@cached(lifetime=86400)
def handle_data_random_music_list_request(self, data: Dict) -> Dict:
ret_str = ""
@ -141,10 +160,12 @@ class CxbRevSunriseS1(CxbBase):
count = 0
for line in lines:
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)
def handle_data_license_request(self, data: Dict) -> Dict:
ret_str = ""
@ -152,54 +173,58 @@ class CxbRevSunriseS1(CxbBase):
lines = licenses.readlines()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str})
return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_course_list_request(self, data: Dict) -> Dict:
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()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str})
return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_csxxxx_request(self, data: Dict) -> Dict:
extra_num = int(data["dldate"]["filetype"][-4:])
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()
for line in lines:
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:
return({"data":""})
return {"data": ""}
def handle_data_mission_bonus_request(self, data: Dict) -> Dict:
return({"data":""})
return {"data": ""}
def handle_data_unlimited_mission_request(self, data: Dict) -> Dict:
return({"data":""})
return {"data": ""}
def handle_data_partner_list_request(self, data: Dict) -> Dict:
ret_str = ""
# 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},1,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},1,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"
for i in range(1,130):
ret_str +=f"{i},100,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):
ret_str += f"{i},100,100,100,100,100,\r\n"
ret_str += "---\r\n"
return({"data": ret_str})
return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_partnerxxxx_request(self, data: Dict) -> Dict:
partner_num = int(data["dldate"]["filetype"][-4:])
@ -208,50 +233,54 @@ class CxbRevSunriseS1(CxbBase):
lines = partner.readlines()
for line in lines:
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:
return({"data": True})
return {"data": True}
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:
#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 += 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"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"
return({"data": ret_str})
return {"data": ret_str}
def handle_data_stxxxx_request(self, data: Dict) -> Dict:
story_num = int(data["dldate"]["filetype"][-4:])
ret_str = ""
for i in range(1,11):
ret_str +=f"{i},st000{story_num}_{i-1},,,,,,,,,,,,,,,,1,,-1,1,\r\n"
return({"data": ret_str})
for i in range(1, 11):
ret_str += f"{i},st000{story_num}_{i-1},,,,,,,,,,,,,,,,1,,-1,1,\r\n"
return {"data": ret_str}
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:
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:
return({"data":""})
return {"data": ""}
def handle_data_event_detail_list_request(self, data: Dict) -> Dict:
event_id = data["dldate"]["filetype"].split("/")[2]
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:
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:
return({"data":""})
return {"data": ""}
def handle_data_event_stamp_map_list_csxxxx_request(self, data: Dict) -> Dict:
event_id = data["dldate"]["filetype"].split("/")[2]
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:
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.const import CxbConstants
class CxbRevSunriseS2(CxbBase):
def __init__(self, cfg: CoreConfig, game_cfg: CxbConfig) -> None:
super().__init__(cfg, game_cfg)
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:
ret_str = ""
with open(r"titles/cxb/rss2_data/MusicArchiveList.csv") as music:
lines = music.readlines()
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"
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:
#ItemListIcon load
# ItemListIcon load
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()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
#ItemListTitle load
# ItemListTitle load
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()
for line in lines:
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:
#ShopListIcon load
# ShopListIcon load
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()
for line in lines:
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:
ret_str = ""
with open(r"titles/cxb/rss2_data/NewsList.csv", encoding="UTF-8") as news:
lines = news.readlines()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str})
return {"data": ret_str}
def handle_data_tips_request(self, data: Dict) -> Dict:
return({"data":""})
return {"data": ""}
def handle_data_release_info_list_request(self, data: Dict) -> Dict:
return({"data":""})
return {"data": ""}
@cached(lifetime=86400)
def handle_data_random_music_list_request(self, data: Dict) -> Dict:
ret_str = ""
@ -141,10 +160,12 @@ class CxbRevSunriseS2(CxbBase):
count = 0
for line in lines:
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)
def handle_data_license_request(self, data: Dict) -> Dict:
ret_str = ""
@ -152,54 +173,58 @@ class CxbRevSunriseS2(CxbBase):
lines = licenses.readlines()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str})
return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_course_list_request(self, data: Dict) -> Dict:
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()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str})
return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_csxxxx_request(self, data: Dict) -> Dict:
extra_num = int(data["dldate"]["filetype"][-4:])
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()
for line in lines:
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:
return({"data":""})
return {"data": ""}
def handle_data_mission_bonus_request(self, data: Dict) -> Dict:
return({"data":""})
return {"data": ""}
def handle_data_unlimited_mission_request(self, data: Dict) -> Dict:
return({"data":""})
return {"data": ""}
def handle_data_partner_list_request(self, data: Dict) -> Dict:
ret_str = ""
# 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},1,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},1,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"
for i in range(1,130):
ret_str +=f"{i},100,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):
ret_str += f"{i},100,100,100,100,100,\r\n"
ret_str += "---\r\n"
return({"data": ret_str})
return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_partnerxxxx_request(self, data: Dict) -> Dict:
partner_num = int(data["dldate"]["filetype"][-4:])
@ -208,55 +233,65 @@ class CxbRevSunriseS2(CxbBase):
lines = partner.readlines()
for line in lines:
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:
return({"data": True})
return {"data": True}
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:
#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 += 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"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"
return({"data": ret_str})
return {"data": ret_str}
def handle_data_stxxxx_request(self, data: Dict) -> Dict:
story_num = int(data["dldate"]["filetype"][-4:])
ret_str = ""
# 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):
ret_str +=f"{i},st000{story_num}_{i-1},,,,,,,,,,,,,,,,1,,-1,1,\r\n"
return({"data": ret_str})
for i in range(1, 11):
ret_str += f"{i},st000{story_num}_{i-1},,,,,,,,,,,,,,,,1,,-1,1,\r\n"
return {"data": ret_str}
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:
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:
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:
event_id = data["dldate"]["filetype"].split("/")[2]
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:
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:
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:
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:
return({"data":""})
return {"data": ""}
def handle_data_event_stamp_map_list_csxxxx_request(self, data: Dict) -> Dict:
event_id = data["dldate"]["filetype"].split("/")[2]
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:
return({"data":""})
return {"data": ""}

View File

@ -14,32 +14,29 @@ energy = Table(
Column("user", ForeignKey("aime_user.id", ondelete="cascade"), nullable=False),
Column("energy", Integer, nullable=False, server_default="0"),
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(
energy = rev_energy
)
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(energy=rev_energy)
result = self.execute(conflict)
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 result.lastrowid
def get_energy(self, user_id: int) -> Optional[Dict]:
sql = energy.select(
and_(energy.c.user == user_id)
)
sql = energy.select(and_(energy.c.user == user_id))
result = self.execute(sql)
if result is None: return None
if result is None:
return None
return result.fetchone()

View File

@ -16,57 +16,63 @@ profile = Table(
Column("index", Integer, nullable=False),
Column("data", JSON, nullable=False),
UniqueConstraint("user", "index", name="cxb_profile_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
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(
user = user_id,
version = version,
index = index,
data = data
user=user_id, version=version, index=index, data=data
)
conflict = sql.on_duplicate_key_update(
index = index,
data = data
)
conflict = sql.on_duplicate_key_update(index=index, data=data)
result = self.execute(conflict)
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 result.lastrowid
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
"""
sql = profile.select(and_(
profile.c.version == version,
profile.c.user == aime_id
))
sql = profile.select(
and_(profile.c.version == version, profile.c.user == aime_id)
)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
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
"""
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.user == aime_id,
profile.c.index == index
))
profile.c.index == index,
)
)
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
result = self.execute(sql)
if result is None: return None
if result is None:
return None
return result.fetchone()

View File

@ -18,7 +18,7 @@ score = Table(
Column("song_index", Integer),
Column("data", JSON),
UniqueConstraint("user", "song_mcode", "song_index", name="cxb_score_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
playlog = Table(
@ -40,7 +40,7 @@ playlog = Table(
Column("fail", Integer),
Column("combo", Integer),
Column("date_scored", TIMESTAMP, server_default=func.now()),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
ranking = Table(
@ -53,11 +53,19 @@ ranking = Table(
Column("score", Integer),
Column("clear", Integer),
UniqueConstraint("user", "rev_id", name="cxb_ranking_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
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
"""
@ -66,22 +74,37 @@ class CxbScoreData(BaseData):
song_mcode=song_mcode,
game_version=game_version,
song_index=song_index,
data=data
data=data,
)
conflict = sql.on_duplicate_key_update(
data = sql.inserted.data
)
conflict = sql.on_duplicate_key_update(data=sql.inserted.data)
result = self.execute(conflict)
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 result.lastrowid
def put_playlog(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]:
def put_playlog(
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
"""
@ -99,45 +122,42 @@ class CxbScoreData(BaseData):
slow=this_slow,
slow2=this_slow2,
fail=fail,
combo=combo
combo=combo,
)
result = self.execute(sql)
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 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
"""
if song_id == 0:
sql = insert(ranking).values(
user=user_id,
rev_id=rev_id,
score=score,
clear=clear
user=user_id, rev_id=rev_id, score=score, clear=clear
)
else:
sql = insert(ranking).values(
user=user_id,
rev_id=rev_id,
song_id=song_id,
score=score,
clear=clear
user=user_id, rev_id=rev_id, song_id=song_id, score=score, clear=clear
)
conflict = sql.on_duplicate_key_update(
score = score
)
conflict = sql.on_duplicate_key_update(score=score)
result = self.execute(conflict)
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 result.lastrowid
def get_best_score(self, user_id: int, song_mcode: int) -> Optional[Dict]:
@ -146,21 +166,22 @@ class CxbScoreData(BaseData):
)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
return result.fetchone()
def get_best_scores(self, user_id: int) -> Optional[Dict]:
sql = score.select(score.c.user == user_id)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
return result.fetchall()
def get_best_rankings(self, user_id: int) -> Optional[List[Dict]]:
sql = ranking.select(
ranking.c.user == user_id
)
sql = ranking.select(ranking.c.user == user_id)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
return result.fetchall()

View File

@ -21,54 +21,75 @@ music = Table(
Column("artist", String(255)),
Column("category", String(255)),
Column("level", Float),
UniqueConstraint("version", "songId", "chartId", "index", name="cxb_static_music_uk"),
mysql_charset='utf8mb4'
UniqueConstraint(
"version", "songId", "chartId", "index", name="cxb_static_music_uk"
),
mysql_charset="utf8mb4",
)
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(
version = version,
songId = mcode,
index = index,
chartId = chart,
title = title,
artist = artist,
category = category,
level = level
version=version,
songId=mcode,
index=index,
chartId=chart,
title=title,
artist=artist,
category=category,
level=level,
)
conflict = sql.on_duplicate_key_update(
title = title,
artist = artist,
category = category,
level = level
title=title, artist=artist, category=category, level=level
)
result = self.execute(conflict)
if result is None: return None
if result is None:
return None
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:
sql = select(music).where(music.c.version == version)
else:
sql = select(music).where(and_(
music.c.version == version,
music.c.songId == song_id,
))
sql = select(music).where(
and_(
music.c.version == version,
music.c.songId == song_id,
)
)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
return result.fetchall()
def get_music_chart(self, version: int, song_id: int, chart_id: int) -> Optional[List[Row]]:
sql = select(music).where(and_(
music.c.version == version,
music.c.songId == song_id,
music.c.chartId == chart_id
))
def get_music_chart(
self, version: int, song_id: int, chart_id: int
) -> Optional[List[Row]]:
sql = select(music).where(
and_(
music.c.version == version,
music.c.songId == song_id,
music.c.chartId == chart_id,
)
)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
return result.fetchone()

View File

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

View File

@ -1,6 +1,6 @@
import datetime
from typing import Any, List, Dict
import logging
import logging
import json
import urllib
@ -9,34 +9,35 @@ from titles.diva.config import DivaConfig
from titles.diva.const import DivaConstants
from titles.diva.database import DivaData
class DivaBase():
class DivaBase:
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.data = DivaData(cfg) # Database
self.data = DivaData(cfg) # Database
self.date_time_format = "%Y-%m-%d %H:%M:%S"
self.logger = logging.getLogger("diva")
self.game = DivaConstants.GAME_CODE
self.version = DivaConstants.VER_PROJECT_DIVA_ARCADE_FUTURE_TONE
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:
return ""
def handle_game_init_request(self, data: Dict) -> Dict:
return ( f'' )
return f""
def handle_attend_request(self, data: Dict) -> Dict:
encoded = "&"
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_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_lut': f'{self.time_lut}',
"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_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}",
}
encoded += urllib.parse.urlencode(params)
encoded = encoded.replace("%2C", ",")
@ -45,42 +46,42 @@ class DivaBase():
def handle_ping_request(self, data: Dict) -> Dict:
encoded = "&"
params = {
'ping_b_msg': f'Welcome to {self.core_cfg.server.name} network!',
'ping_m_msg': 'xxx',
'atnd_lut': f'{self.time_lut}',
'fi_lut': f'{self.time_lut}',
'ci_lut': f'{self.time_lut}',
'qi_lut': f'{self.time_lut}',
'pvl_lut': '2021-05-22 12:08:16.0',
'shp_ctlg_lut': '2020-06-10 19:44:16.0',
'cstmz_itm_ctlg_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_ps_lut': f'{self.time_lut}',
'bi_lut': '2020-09-18 10:00:00.0',
'cpi_lut': '2020-10-25 09:25:10.0',
'bdlol_lut': '2020-09-18 10:00:00.0',
'p_std_hc_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',
'pnml_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',
'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_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_prm4': '-1,-1,-1,-1,-1,-1,-1,-1,-1,-1',
'pow_save_flg': 0,
'nblss_dnt_p': 100,
'nblss_ltt_rl_vp': 1500,
'nblss_ex_ltt_flg': 1,
'nblss_dnt_st_tm': "2019-07-15 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_ed_tm': "2019-09-22 12:00:00.0",
"ping_b_msg": f"Welcome to {self.core_cfg.server.name} network!",
"ping_m_msg": "xxx",
"atnd_lut": f"{self.time_lut}",
"fi_lut": f"{self.time_lut}",
"ci_lut": f"{self.time_lut}",
"qi_lut": f"{self.time_lut}",
"pvl_lut": "2021-05-22 12:08:16.0",
"shp_ctlg_lut": "2020-06-10 19:44:16.0",
"cstmz_itm_ctlg_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_ps_lut": f"{self.time_lut}",
"bi_lut": "2020-09-18 10:00:00.0",
"cpi_lut": "2020-10-25 09:25:10.0",
"bdlol_lut": "2020-09-18 10:00:00.0",
"p_std_hc_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",
"pnml_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",
"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_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_prm4": "-1,-1,-1,-1,-1,-1,-1,-1,-1,-1",
"pow_save_flg": 0,
"nblss_dnt_p": 100,
"nblss_ltt_rl_vp": 1500,
"nblss_ex_ltt_flg": 1,
"nblss_dnt_st_tm": "2019-07-15 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_ed_tm": "2019-09-22 12:00:00.0",
}
encoded += urllib.parse.urlencode(params)
encoded = encoded.replace("+", "%20")
encoded = encoded.replace("%2C", ",")
@ -122,7 +123,7 @@ class DivaBase():
response += f"&pvl_lut={self.time_lut}"
response += f"&pv_lst={pvlist}"
return ( response )
return response
def handle_shop_catalog_request(self, data: Dict) -> Dict:
catalog = ""
@ -137,7 +138,21 @@ class DivaBase():
else:
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) + ","
catalog += f"{urllib.parse.quote(line)}"
@ -146,7 +161,7 @@ class DivaBase():
response = f"&shp_ctlg_lut={self.time_lut}"
response += f"&shp_ctlg={catalog[:-3]}"
return ( response )
return response
def handle_buy_module_request(self, data: Dict) -> Dict:
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"])
self.data.profile.update_profile(
profile["user"],
vcld_pts=new_vcld_pts
)
self.data.profile.update_profile(profile["user"], vcld_pts=new_vcld_pts)
self.data.module.put_module(data["pd_id"], self.version, data["mdl_id"])
# generate the mdl_have string
@ -191,7 +203,21 @@ class DivaBase():
else:
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) + ","
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={catalog[:-3]}"
return ( response )
return response
def handle_buy_cstmz_itm_request(self, data: Dict) -> Dict:
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
if not item:
@ -217,15 +245,16 @@ class DivaBase():
new_vcld_pts = profile["vcld_pts"] - int(data["cstmz_itm_price"])
# save new Vocaloid Points balance
self.data.profile.update_profile(
profile["user"],
vcld_pts=new_vcld_pts
self.data.profile.update_profile(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
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 += f"&cstmz_itm_id={data['cstmz_itm_id']}"
@ -237,33 +266,33 @@ class DivaBase():
def handle_festa_info_request(self, data: Dict) -> Dict:
encoded = "&"
params = {
'fi_id': '1,-1',
'fi_name': f'{self.core_cfg.server.name} Opening,xxx',
'fi_kind': '0,0',
'fi_difficulty': '-1,-1',
'fi_pv_id_lst': 'ALL,ALL',
'fi_attr': '7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF',
'fi_add_vp': '20,0',
'fi_mul_vp': '1,1',
'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_lut': '{self.time_lut}',
"fi_id": "1,-1",
"fi_name": f"{self.core_cfg.server.name} Opening,xxx",
"fi_kind": "0,0",
"fi_difficulty": "-1,-1",
"fi_pv_id_lst": "ALL,ALL",
"fi_attr": "7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
"fi_add_vp": "20,0",
"fi_mul_vp": "1,1",
"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_lut": "{self.time_lut}",
}
encoded += urllib.parse.urlencode(params)
encoded = encoded.replace("+", "%20")
encoded = encoded.replace("%2C", ",")
return encoded
def handle_contest_info_request(self, data: Dict) -> Dict:
response = ""
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"
return ( response )
return response
def handle_qst_inf_request(self, data: Dict) -> Dict:
quest = ""
@ -279,11 +308,31 @@ class DivaBase():
response += f"&qhi_str={quest[:-1]}"
else:
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,"
responseline = f"{quest[:-1]},"
for i in range(len(questList),59):
for i in range(len(questList), 59):
responseline += "%2A%2A%2A%0A,"
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"
return ( response )
return response
def handle_nv_ranking_request(self, data: Dict) -> Dict:
return ( f'' )
return f""
def handle_ps_ranking_request(self, data: Dict) -> Dict:
return ( f'' )
return f""
def handle_ng_word_request(self, data: Dict) -> Dict:
return ( f'' )
return f""
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:
return ( f'' )
return f""
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:
return ( f'' )
return f""
def handle_banner_info_request(self, data: Dict) -> Dict:
return ( f'' )
return f""
def handle_banner_data_request(self, data: Dict) -> Dict:
return ( f'' )
return f""
def handle_cm_ply_info_request(self, data: Dict) -> Dict:
return ( f'' )
return f""
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:
return ( f'' )
return f""
def handle_pre_start_request(self, data: Dict) -> str:
profile = self.data.profile.get_profile(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
def handle_registration_request(self, data: Dict) -> Dict:
self.data.profile.create_profile(self.version, data["aime_id"], data["player_name"])
return (f"&cd_adm_result=1&pd_id={data['aime_id']}")
self.data.profile.create_profile(
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:
profile = self.data.profile.get_profile(data["pd_id"], self.version)
@ -384,12 +435,16 @@ class DivaBase():
mdl_have = "F" * 250
# generate the mdl_have string if "unlock_all_modules" is disabled
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
# generate the cstmz_itm_have string if "unlock_all_items" is disabled
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 += "&start_result=1"
@ -452,15 +507,16 @@ class DivaBase():
response += f"&mdl_eqp_ary={mdl_eqp_ary}"
response += f"&c_itm_eqp_ary={c_itm_eqp_ary}"
response += f"&ms_itm_flg_ary={ms_itm_flg_ary}"
return ( response )
return response
def handle_pd_unlock_request(self, data: Dict) -> Dict:
return ( f'' )
return f""
def handle_spend_credit_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile(data["pd_id"], self.version)
if profile is None: return
if profile is None:
return
response = ""
@ -471,10 +527,16 @@ class DivaBase():
response += f"&lv_efct_id={profile['lv_efct_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,
pd_db_customize: Dict, edition: int) -> str:
def _get_pv_pd_result(
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
"""
@ -483,7 +545,7 @@ class DivaBase():
# make sure there are enough max scores to calculate a ranking
if pd_db_ranking["ranking"] != 0:
global_ranking = pd_db_ranking["ranking"]
# pv_no
pv_result = f"{song},"
# edition
@ -513,7 +575,7 @@ class DivaBase():
f"{pd_db_customize['chsld_se']},"
f"{pd_db_customize['sldtch_se']}"
)
pv_result += f"{module_eqp},"
pv_result += f"{customize_eqp},"
pv_result += f"{customize_flag},"
@ -537,21 +599,35 @@ class DivaBase():
if int(song) > 0:
# the request do not send a edition so just perform a query best score and ranking for each edition.
# 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_1 = self.data.score.get_best_user_score(data["pd_id"], int(song), data["difficulty"], edition=1)
pd_db_song_0 = self.data.score.get_best_user_score(
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
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:
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
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(int(song), pd_db_song_1, pd_db_ranking_1, pd_db_customize, edition=1)
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(
int(song), pd_db_song_1, pd_db_ranking_1, pd_db_customize, edition=1
)
self.logger.debug(f"pv_result = {pv_result}")
@ -565,13 +641,12 @@ class DivaBase():
response += "&pdddt_flg=0"
response += f"&pdddt_tm={self.time_lut}"
return ( response )
return response
def handle_stage_start_request(self, data: Dict) -> Dict:
return ( f'' )
return f""
def handle_stage_result_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile(data["pd_id"], self.version)
pd_song_list = data["stg_ply_pv_id"].split(",")
@ -590,15 +665,100 @@ class DivaBase():
for index, value in enumerate(pd_song_list):
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:
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_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_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_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"]):
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_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_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_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"]):
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
@ -608,7 +768,7 @@ class DivaBase():
total_atn_pnt = 0
for best_score in best_scores:
total_atn_pnt += best_score["atn_pnt"]
new_level = (total_atn_pnt // 13979) + 1
new_level_pnt = round((total_atn_pnt % 13979) / 13979 * 100)
@ -630,7 +790,7 @@ class DivaBase():
nxt_dffclty=int(data["nxt_dffclty"]),
nxt_edtn=int(data["nxt_edtn"]),
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}"
@ -663,35 +823,51 @@ class DivaBase():
response += "&my_ccd_r_hnd=-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:
profile = self.data.profile.get_profile(data["pd_id"], self.version)
self.data.profile.update_profile(
profile["user"],
my_qst_id=data["my_qst_id"],
my_qst_sts=data["my_qst_sts"]
profile["user"], 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:
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:
self.data.pv_customize.put_pv_customize(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"])
self.data.pv_customize.put_pv_customize(
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:
self.data.pv_customize.put_pv_customize(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")
self.data.pv_customize.put_pv_customize(
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"
return ( response )
return response
def handle_card_procedure_request(self, data: Dict) -> str:
profile = self.data.profile.get_profile(data["aime_id"], self.version)
if profile is None:
return "&cd_adm_result=0"
response = "&cd_adm_result=1"
response += "&chg_name_price=100"
response += "&accept_idx=100"
@ -706,20 +882,18 @@ class DivaBase():
response += f"&passwd_stat={profile['passwd_stat']}"
return response
def handle_change_name_request(self, data: Dict) -> str:
profile = self.data.profile.get_profile(data["pd_id"], self.version)
# make sure user has enough Vocaloid Points
if profile["vcld_pts"] < int(data["chg_name_price"]):
return "&cd_adm_result=0"
# update the vocaloid points and player name
new_vcld_pts = profile["vcld_pts"] - int(data["chg_name_price"])
self.data.profile.update_profile(
profile["user"],
player_name=data["player_name"],
vcld_pts=new_vcld_pts
profile["user"], player_name=data["player_name"], vcld_pts=new_vcld_pts
)
response = "&cd_adm_result=1"
@ -728,19 +902,17 @@ class DivaBase():
response += f"&player_name={data['player_name']}"
return response
def handle_change_passwd_request(self, data: Dict) -> str:
profile = self.data.profile.get_profile(data["pd_id"], self.version)
# TODO: return correct error number instead of 0
if (data["passwd"] != profile["passwd"]):
if data["passwd"] != profile["passwd"]:
return "&cd_adm_result=0"
# set password to true and update the saved password
self.data.profile.update_profile(
profile["user"],
passwd_stat=1,
passwd=data["new_passwd"]
profile["user"], passwd_stat=1, passwd=data["new_passwd"]
)
response = "&cd_adm_result=1"

View File

@ -1,30 +1,40 @@
from core.config import CoreConfig
class DivaServerConfig():
class DivaServerConfig:
def __init__(self, parent_config: "DivaConfig") -> None:
self.__config = parent_config
@property
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
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:
self.__config = parent_config
@property
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
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):

View File

@ -1,4 +1,4 @@
class DivaConstants():
class DivaConstants:
GAME_CODE = "SBZV"
CONFIG_NAME = "diva.yaml"
@ -10,4 +10,4 @@ class DivaConstants():
@classmethod
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.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):

View File

@ -14,85 +14,112 @@ from titles.diva.config import DivaConfig
from titles.diva.const import DivaConstants
from titles.diva.base import DivaBase
class DivaServlet():
class DivaServlet:
def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None:
self.core_cfg = core_cfg
self.game_cfg = DivaConfig()
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.logger = logging.getLogger("diva")
log_fmt_str = "[%(asctime)s] Diva | %(levelname)s | %(message)s"
log_fmt = logging.Formatter(log_fmt_str)
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.core_cfg.server.log_dir, "diva"), encoding='utf8',
when="d", backupCount=10)
fileHandler = TimedRotatingFileHandler(
"{0}/{1}.log".format(self.core_cfg.server.log_dir, "diva"),
encoding="utf8",
when="d",
backupCount=10,
)
fileHandler.setFormatter(log_fmt)
consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt)
self.logger.addHandler(fileHandler)
self.logger.addHandler(consoleHandler)
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
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()
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:
return (False, "", "")
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/", "")
def render_POST(self, req: Request, version: int, url_path: str) -> bytes:
req_raw = req.content.getvalue()
url_header = req.getAllHeaders()
#Ping Dispatch
if "THIS_STRING_SEPARATES"in str(url_header):
# Ping Dispatch
if "THIS_STRING_SEPARATES" in str(url_header):
binary_request = req_raw.splitlines()
binary_cmd_decoded = binary_request[3].decode("utf-8")
binary_array = binary_cmd_decoded.split('&')
binary_array = binary_cmd_decoded.split("&")
bin_req_data = {}
for kvp in binary_array:
split_bin = kvp.split("=")
bin_req_data[split_bin[0]] = split_bin[1]
self.logger.info(f"Binary {bin_req_data['cmd']} Request")
self.logger.debug(bin_req_data)
handler = getattr(self.base, f"handle_{bin_req_data['cmd']}_request")
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}")
return f"cmd={bin_req_data['cmd']}&req_id={bin_req_data['req_id']}&stat=ok{resp}".encode('utf-8')
self.logger.debug(
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:
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:
self.logger.error(f"Failed to defalte! {e} -> {gz_string}")
return "stat=0"
req_kvp = urllib.parse.unquote(url_data)
req_data = {}
# We then need to split each parts with & so we can reuse them to fill out the requests
splitted_request = str.split(req_kvp, "&")
for kvp in splitted_request:
@ -109,15 +136,25 @@ class DivaServlet():
handler = getattr(self.base, func_to_find)
resp = handler(req_data)
except AttributeError as e:
except AttributeError as 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:
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")
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.const import DivaConstants
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)
self.data = DivaData(config)
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:
self.logger.error(f"Invalid project diva version {version}")
exit(1)
@ -30,7 +40,7 @@ class DivaReader(BaseReader):
if not path.exists(f"{self.bin_dir}/rom"):
self.logger.warn(f"Couldn't find rom folder in {self.bin_dir}, skipping")
pull_bin_rom = False
if self.opt_dir is not None:
opt_dirs = self.get_data_directories(self.opt_dir)
else:
@ -44,18 +54,25 @@ class DivaReader(BaseReader):
if pull_opt_rom:
for dir in opt_dirs:
self.read_rom(f"{dir}/rom")
def read_ram(self, ram_root_dir: str) -> None:
self.logger.info(f"Read RAM from {ram_root_dir}")
if path.exists(f"{ram_root_dir}/databank"):
for root, dirs, files in walk(f"{ram_root_dir}/databank"):
for file in files:
if file.startswith("ShopCatalog_") or file.startswith("CustomizeItemCatalog_") or \
(file.startswith("QuestInfo") and not file.startswith("QuestInfoTm")):
if (
file.startswith("ShopCatalog_")
or file.startswith("CustomizeItemCatalog_")
or (
file.startswith("QuestInfo")
and not file.startswith("QuestInfoTm")
)
):
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 == "***":
self.logger.info(f"{file} is empty, skipping")
continue
@ -70,23 +87,54 @@ class DivaReader(BaseReader):
if file.startswith("ShopCatalog_"):
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],
split[x+1], split[x+4], split[x+5])
self.data.static.put_shop(
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):
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],
split[x+1], split[x+4], split[x+5])
self.data.static.put_items(
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:
self.logger.info(f"Added quest {split[0]}")
self.data.static.put_quests(self.version, split[0], split[6], split[2], split[3],
split[7], split[8], split[1], split[4], split[5])
self.data.static.put_quests(
self.version,
split[0],
split[6],
split[2],
split[3],
split[7],
split[8],
split[1],
split[4],
split[5],
)
else:
continue
@ -102,13 +150,13 @@ class DivaReader(BaseReader):
elif path.exists(f"{rom_root_dir}/pv_db.txt"):
file_path = f"{rom_root_dir}/pv_db.txt"
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
with open(file_path, "r", encoding="utf-8") as f:
for line in f.readlines():
if line.startswith("#") or not line:
continue
@ -127,14 +175,13 @@ class DivaReader(BaseReader):
for x in range(1, len(key_split)):
key_args.append(key_split[x])
try:
pv_list[pv_id] = self.add_branch(pv_list[pv_id], key_args, val)
except KeyError:
pv_list[pv_id] = {}
pv_list[pv_id] = self.add_branch(pv_list[pv_id], key_args, val)
for pv_id, pv_data in pv_list.items():
song_id = int(pv_id.split("_")[1])
if "songinfo" not in pv_data:
@ -148,46 +195,99 @@ class DivaReader(BaseReader):
if "music" not in pv_data["songinfo"]:
pv_data["songinfo"]["music"] = "-"
if "easy" in pv_data['difficulty'] and '0' in pv_data['difficulty']['easy']:
diff = pv_data['difficulty']['easy']['0']['level'].split('_')
if "easy" in pv_data["difficulty"] and "0" in pv_data["difficulty"]["easy"]:
diff = pv_data["difficulty"]["easy"]["0"]["level"].split("_")
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"],
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.data.static.put_music(
self.version,
song_id,
0,
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 (
"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.data.static.put_music(self.version, song_id, 1, 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 "hard" in pv_data['difficulty'] and '0' in pv_data['difficulty']['hard']:
diff = pv_data['difficulty']['hard']['0']['level'].split('_')
self.data.static.put_music(
self.version,
song_id,
1,
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 "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.data.static.put_music(self.version, song_id, 2, 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 "extreme" in pv_data['difficulty']:
if "0" in pv_data['difficulty']['extreme']:
diff = pv_data['difficulty']['extreme']['0']['level'].split('_')
self.data.static.put_music(
self.version,
song_id,
2,
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 "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.data.static.put_music(self.version, 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"])
self.data.static.put_music(
self.version,
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']:
diff = pv_data['difficulty']['extreme']['1']['level'].split('_')
if "1" in pv_data["difficulty"]["extreme"]:
diff = pv_data["difficulty"]["extreme"]["1"]["level"].split("_")
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"],
pv_data["songinfo"]["illustrator"], pv_data["songinfo"]["lyrics"], pv_data["songinfo"]["music"],
float(f"{diff[2]}.{diff[3]}"), pv_data["bpm"], pv_data["date"])
self.data.static.put_music(
self.version,
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):
"""
@ -195,9 +295,9 @@ class DivaReader(BaseReader):
Author: iJames on StackOverflow
"""
key = vector[0]
tree[key] = value \
if len(vector) == 1 \
else self.add_branch(tree[key] if key in tree else {},
vector[1:],
value)
return tree
tree[key] = (
value
if len(vector) == 1
else self.add_branch(tree[key] if key in tree else {}, vector[1:], value)
)
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.static import DivaStaticData
__all__ = [DivaProfileData, DivaScoreData, DivaModuleData,
DivaCustomizeItemData, DivaPvCustomizeData, DivaItemData,
DivaStaticData]
__all__ = [
DivaProfileData,
DivaScoreData,
DivaModuleData,
DivaCustomizeItemData,
DivaPvCustomizeData,
DivaItemData,
DivaStaticData,
]

View File

@ -10,25 +10,29 @@ customize = Table(
"diva_profile_customize_item",
metadata,
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("item_id", Integer, nullable=False),
UniqueConstraint("user", "version", "item_id", name="diva_profile_customize_item_uk"),
mysql_charset='utf8mb4'
UniqueConstraint(
"user", "version", "item_id", name="diva_profile_customize_item_uk"
),
mysql_charset="utf8mb4",
)
class DivaCustomizeItemData(BaseData):
def put_customize_item(self, aime_id: int, version: int, item_id: int) -> None:
sql = insert(customize).values(
version=version,
user=aime_id,
item_id=item_id
)
sql = insert(customize).values(version=version, user=aime_id, item_id=item_id)
result = self.execute(sql)
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 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
"""
sql = customize.select(and_(
customize.c.version == version,
customize.c.user == aime_id
))
sql = customize.select(
and_(customize.c.version == version, customize.c.user == aime_id)
)
result = self.execute(sql)
if result is None:

View File

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

View File

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

View File

@ -11,8 +11,11 @@ profile = Table(
"diva_profile",
metadata,
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("player_name", String(10), nullable=False),
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_btn_se_eqp", Boolean, nullable=False, server_default="1"),
Column("use_pv_sld_se_eqp", Boolean, nullable=False, server_default="0"),
Column("use_pv_chn_sld_se_eqp", Boolean,
nullable=False, server_default="0"),
Column("use_pv_sldr_tch_se_eqp", Boolean,
nullable=False, server_default="0"),
Column("use_pv_chn_sld_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_dffclty", Integer, nullable=False, server_default="2"),
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("passwd_stat", Integer, nullable=False, server_default="0"),
Column("passwd", String(12), nullable=False, server_default="**********"),
Column("my_qst_id", 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"),
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"),
Column(
"my_qst_id",
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",
),
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"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
class DivaProfileData(BaseData):
def create_profile(self, version: int, aime_id: int,
player_name: str) -> Optional[int]:
def create_profile(
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
"""
sql = insert(profile).values(
version=version,
user=aime_id,
player_name=player_name
version=version, user=aime_id, player_name=player_name
)
conflict = sql.on_duplicate_key_update(
player_name=sql.inserted.player_name
)
conflict = sql.on_duplicate_key_update(player_name=sql.inserted.player_name)
result = self.execute(conflict)
if result is None:
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 result.lastrowid
@ -86,17 +91,17 @@ class DivaProfileData(BaseData):
result = self.execute(sql)
if result is None:
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
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
"""
sql = profile.select(and_(
profile.c.version == version,
profile.c.user == aime_id
))
sql = profile.select(
and_(profile.c.version == version, profile.c.user == aime_id)
)
result = self.execute(sql)
if result is None:

View File

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

View File

@ -28,7 +28,7 @@ score = Table(
Column("worst", Integer),
Column("max_combo", Integer),
UniqueConstraint("user", "pv_id", "difficulty", "edition", name="diva_score_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
playlog = Table(
@ -51,16 +51,29 @@ playlog = Table(
Column("worst", Integer),
Column("max_combo", Integer),
Column("date_scored", TIMESTAMP, server_default=func.now()),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
class DivaScoreData(BaseData):
def put_best_score(self, user_id: int, game_version: 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]:
def put_best_score(
self,
user_id: int,
game_version: 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
"""
@ -98,16 +111,30 @@ class DivaScoreData(BaseData):
result = self.execute(conflict)
if result is None:
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 result.lastrowid
def put_playlog(self, user_id: int, game_version: 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]:
def put_playlog(
self,
user_id: int,
game_version: 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
"""
@ -126,24 +153,28 @@ class DivaScoreData(BaseData):
safe=safe,
sad=sad,
worst=worst,
max_combo=max_combo
max_combo=max_combo,
)
result = self.execute(sql)
if result is None:
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 result.lastrowid
def get_best_user_score(self, user_id: int, pv_id: int, difficulty: int,
edition: int) -> Optional[Dict]:
def get_best_user_score(
self, user_id: int, pv_id: int, difficulty: int, edition: int
) -> Optional[Dict]:
sql = score.select(
and_(score.c.user == user_id,
score.c.pv_id == pv_id,
score.c.difficulty == difficulty,
score.c.edition == edition)
and_(
score.c.user == user_id,
score.c.pv_id == pv_id,
score.c.difficulty == difficulty,
score.c.edition == edition,
)
)
result = self.execute(sql)
@ -151,36 +182,48 @@ class DivaScoreData(BaseData):
return None
return result.fetchone()
def get_top3_scores(self, pv_id: int, difficulty: int,
edition: int) -> Optional[List[Dict]]:
sql = score.select(
and_(score.c.pv_id == pv_id,
score.c.difficulty == difficulty,
score.c.edition == edition)
).order_by(score.c.score.desc()).limit(3)
def get_top3_scores(
self, pv_id: int, difficulty: int, edition: int
) -> Optional[List[Dict]]:
sql = (
score.select(
and_(
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)
if result is None:
return None
return result.fetchall()
def get_global_ranking(self, user_id: int, pv_id: int, difficulty: int,
edition: int) -> Optional[List]:
def get_global_ranking(
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
# edition
sql_sub = select([score.c.score]).filter(
score.c.user == user_id,
score.c.pv_id == pv_id,
score.c.difficulty == difficulty,
score.c.edition == edition
).scalar_subquery()
sql_sub = (
select([score.c.score])
.filter(
score.c.user == user_id,
score.c.pv_id == pv_id,
score.c.difficulty == difficulty,
score.c.edition == edition,
)
.scalar_subquery()
)
# Perform the main query, also rename the resulting column to ranking
sql = select(func.count(score.c.id).label("ranking")).filter(
score.c.score >= sql_sub,
score.c.pv_id == pv_id,
score.c.difficulty == difficulty,
score.c.edition == edition
score.c.edition == edition,
)
result = self.execute(sql)

View File

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

View File

@ -7,4 +7,4 @@ index = Mai2Servlet
database = Mai2Data
reader = Mai2Reader
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.database import Mai2Data
class Mai2Base():
class Mai2Base:
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
self.core_config = cfg
self.game_config = game_cfg
@ -17,23 +18,27 @@ class Mai2Base():
self.logger = logging.getLogger("mai2")
def handle_get_game_setting_api_request(self, data: Dict):
reboot_start = date.strftime(datetime.now() + timedelta(hours=3), Mai2Constants.DATE_TIME_FORMAT)
reboot_end = date.strftime(datetime.now() + timedelta(hours=4), Mai2Constants.DATE_TIME_FORMAT)
reboot_start = date.strftime(
datetime.now() + timedelta(hours=3), Mai2Constants.DATE_TIME_FORMAT
)
reboot_end = date.strftime(
datetime.now() + timedelta(hours=4), Mai2Constants.DATE_TIME_FORMAT
)
return {
"gameSetting": {
"isMaintenance": "false",
"requestInterval": 10,
"rebootStartTime": reboot_start,
"rebootEndTime": reboot_end,
"movieUploadLimit": 10000,
"movieStatus": 0,
"movieServerUri": "",
"deliverServerUri": "",
"oldServerUri": "",
"usbDlServerUri": "",
"rebootInterval": 0
"gameSetting": {
"isMaintenance": "false",
"requestInterval": 10,
"rebootStartTime": reboot_start,
"rebootEndTime": reboot_end,
"movieUploadLimit": 10000,
"movieStatus": 0,
"movieServerUri": "",
"deliverServerUri": "",
"oldServerUri": "",
"usbDlServerUri": "",
"rebootInterval": 0,
},
"isAouAccession": "true",
"isAouAccession": "true",
}
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:
events = self.data.static.get_enabled_events(self.version)
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:
events_lst.append({
"type": event["type"],
"id": event["eventId"],
"startDate": "2017-12-05 07:00:00.0",
"endDate": "2099-12-31 00:00:00.0"
})
events_lst.append(
{
"type": event["type"],
"id": event["eventId"],
"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:
return {"length": 0, "musicIdList": []}
def handle_get_game_charge_api_request(self, data: Dict) -> Dict:
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 = []
for x in range(len(game_charge_list)):
charge_list.append({
"orderId": x,
"chargeId": game_charge_list[x]["ticketId"],
"price": game_charge_list[x]["price"],
"startDate": "2017-12-05 07:00:00.0",
"endDate": "2099-12-31 00:00:00.0"
})
charge_list.append(
{
"orderId": x,
"chargeId": game_charge_list[x]["ticketId"],
"price": game_charge_list[x]["price"],
"startDate": "2017-12-05 07:00:00.0",
"endDate": "2099-12-31 00:00:00.0",
}
)
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:
p = self.data.profile.get_profile_detail(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()
option = o._asdict()
@ -106,20 +122,24 @@ class Mai2Base():
"lastLoginDate": profile["lastLoginDate"],
"lastPlayDate": profile["lastPlayDate"],
"playerRating": profile["playerRating"],
"nameplateId": 0, # Unused
"nameplateId": 0, # Unused
"iconId": profile["iconId"],
"trophyId": 0, # Unused
"trophyId": 0, # Unused
"partnerId": profile["partnerId"],
"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"],
"isNetMember": profile["isNetMember"],
"dailyBonusDate": profile["dailyBonusDate"],
"headPhoneVolume": option["headPhoneVolume"],
"isInherit": False, # Not sure what this is or does??
"banState": profile["banState"] if profile["banState"] is not None else 0 # New with uni+
"isInherit": False, # Not sure what this is or does??
"banState": profile["banState"]
if profile["banState"] is not None
else 0, # New with uni+
}
def handle_user_login_api_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile_detail(data["userId"], self.version)
@ -137,10 +157,10 @@ class Mai2Base():
"returnCode": 1,
"lastLoginDate": lastLoginDate,
"loginCount": loginCt,
"consecutiveLoginCount": 0, # We don't really have a way to track this...
"loginId": loginCt # Used with the playlog!
"consecutiveLoginCount": 0, # We don't really have a way to track this...
"loginId": loginCt, # Used with the playlog!
}
def handle_upload_user_playlog_api_request(self, data: Dict) -> Dict:
user_id = data["userId"]
playlog = data["userPlaylog"]
@ -154,50 +174,83 @@ class Mai2Base():
if "userData" in upsert and len(upsert["userData"]) > 0:
upsert["userData"][0]["isNetMember"] = 1
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:
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:
for ghost in upsert["userGhost"]:
self.data.profile.put_profile_extend(user_id, self.version, ghost)
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:
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:
for k,v in upsert["userActivityList"][0].items():
for k, v in upsert["userActivityList"][0].items():
for act in v:
self.data.profile.put_profile_activity(user_id, act)
if upsert["isNewCharacterList"] and int(upsert["isNewCharacterList"]) > 0:
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:
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:
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:
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:
for music in upsert["userMusicDetailList"]:
self.data.score.put_best_score(user_id, music)
if upsert["isNewCourseList"] and int(upsert["isNewCourseList"]) > 0:
for course in upsert["userCourseList"]:
self.data.score.put_course(user_id, course)
if upsert["isNewFavoriteList"] and int(upsert["isNewFavoriteList"]) > 0:
for fav in upsert["userFavoriteList"]:
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:
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.pop("id")
profile_dict.pop("user")
profile_dict.pop("version")
return {
"userId": data["userId"],
"userData": profile_dict
}
return {"userId": data["userId"], "userData": profile_dict}
def handle_get_user_extend_api_request(self, data: Dict) -> Dict:
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.pop("id")
extend_dict.pop("user")
extend_dict.pop("version")
return {
"userId": data["userId"],
"userExtend": extend_dict
}
return {"userId": data["userId"], "userExtend": extend_dict}
def handle_get_user_option_api_request(self, data: Dict) -> Dict:
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.pop("id")
options_dict.pop("user")
options_dict.pop("version")
return {
"userId": data["userId"],
"userOption": options_dict
}
return {"userId": data["userId"], "userOption": options_dict}
def handle_get_user_card_api_request(self, data: Dict) -> Dict:
return {"userId": data["userId"], "nextIndex": 0, "userCardList": []}
@ -266,73 +313,83 @@ class Mai2Base():
for x in range(next_idx, data["maxCount"]):
try:
user_item_list.append({"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
user_item_list.append(
{
"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"]:
next_idx = data["nextIndex"] + data["maxCount"] + 1
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:
characters = self.data.item.get_characters(data["userId"])
chara_list = []
for chara in characters:
chara_list.append({
"characterId": chara["character_id"],
"level": chara["level"],
"awakening": chara["awakening"],
"useCount": chara["use_count"],
})
chara_list.append(
{
"characterId": chara["character_id"],
"level": chara["level"],
"awakening": chara["awakening"],
"useCount": chara["use_count"],
}
)
return {"userId": data["userId"], "userCharacterList": chara_list}
def handle_get_user_favorite_api_request(self, data: Dict) -> Dict:
favorites = self.data.item.get_favorites(data["userId"], data["itemKind"])
if favorites is None: return
if favorites is None:
return
userFavs = []
for fav in favorites:
userFavs.append({
"userId": data["userId"],
"itemKind": fav["itemKind"],
"itemIdList": fav["itemIdList"]
})
userFavs.append(
{
"userId": data["userId"],
"itemKind": fav["itemKind"],
"itemIdList": fav["itemIdList"],
}
)
return {
"userId": data["userId"],
"userFavoriteData": userFavs
}
return {"userId": data["userId"], "userFavoriteData": userFavs}
def handle_get_user_ghost_api_request(self, data: Dict) -> Dict:
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.pop("user")
ghost_dict.pop("id")
ghost_dict.pop("version_int")
return {
"userId": data["userId"],
"userGhost": ghost_dict
}
return {"userId": data["userId"], "userGhost": ghost_dict}
def handle_get_user_rating_api_request(self, data: Dict) -> Dict:
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.pop("user")
rating_dict.pop("id")
rating_dict.pop("version")
return {
"userId": data["userId"],
"userRating": rating_dict
}
return {"userId": data["userId"], "userRating": rating_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)
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 = []
mlst = []
for play in playlist:
tmp = play._asdict()
tmp = play._asdict()
tmp["id"] = tmp["activityId"]
tmp.pop("activityId")
tmp.pop("user")
plst.append(tmp)
for music in musiclist:
tmp = music._asdict()
tmp = music._asdict()
tmp["id"] = tmp["activityId"]
tmp.pop("activityId")
tmp.pop("user")
mlst.append(tmp)
return {
"userActivity": {
"playList": plst,
"musicList": mlst
}
}
return {"userActivity": {"playList": plst, "musicList": mlst}}
def handle_get_user_course_api_request(self, data: Dict) -> Dict:
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"]):
try:
friend_season_ranking_list.append({
"mapId": friend_season_ranking_list[x]["map_id"],
"distance": friend_season_ranking_list[x]["distance"],
"isLock": friend_season_ranking_list[x]["is_lock"],
"isClear": friend_season_ranking_list[x]["is_clear"],
"isComplete": friend_season_ranking_list[x]["is_complete"],
})
friend_season_ranking_list.append(
{
"mapId": friend_season_ranking_list[x]["map_id"],
"distance": friend_season_ranking_list[x]["distance"],
"isLock": friend_season_ranking_list[x]["is_lock"],
"isClear": friend_season_ranking_list[x]["is_clear"],
"isComplete": friend_season_ranking_list[x]["is_complete"],
}
)
except:
break
# 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"]
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:
maps = self.data.item.get_maps(data["userId"])
@ -412,21 +474,30 @@ class Mai2Base():
for x in range(data["nextIndex"], data["maxCount"] + data["nextIndex"]):
try:
map_list.append({
"mapId": maps[x]["map_id"],
"distance": maps[x]["distance"],
"isLock": maps[x]["is_lock"],
"isClear": maps[x]["is_clear"],
"isComplete": maps[x]["is_complete"],
})
map_list.append(
{
"mapId": maps[x]["map_id"],
"distance": maps[x]["distance"],
"isLock": maps[x]["is_lock"],
"isClear": maps[x]["is_clear"],
"isComplete": maps[x]["is_complete"],
}
)
except:
break
# 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"]
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:
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"]):
try:
login_bonus_list.append({
"bonusId": login_bonuses[x]["bonus_id"],
"point": login_bonuses[x]["point"],
"isCurrent": login_bonuses[x]["is_current"],
"isComplete": login_bonuses[x]["is_complete"],
})
login_bonus_list.append(
{
"bonusId": login_bonuses[x]["bonus_id"],
"point": login_bonuses[x]["point"],
"isCurrent": login_bonuses[x]["is_current"],
"isComplete": login_bonuses[x]["is_complete"],
}
)
except:
break
# 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"]
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:
return {"userId": data["userId"], "length": 0, "userRegionList": []}
@ -460,18 +540,24 @@ class Mai2Base():
if songs is not None:
for song in songs:
music_detail_list.append({
"musicId": song["song_id"],
"level": song["chart_id"],
"playCount": song["play_count"],
"achievement": song["achievement"],
"comboStatus": song["combo_status"],
"syncStatus": song["sync_status"],
"deluxscoreMax": song["dx_score"],
"scoreRank": song["score_rank"],
})
music_detail_list.append(
{
"musicId": song["song_id"],
"level": song["chart_id"],
"playCount": song["play_count"],
"achievement": song["achievement"],
"comboStatus": song["combo_status"],
"syncStatus": song["sync_status"],
"deluxscoreMax": song["dx_score"],
"scoreRank": song["score_rank"],
}
)
if len(music_detail_list) == data["maxCount"]:
next_index = data["maxCount"] + data["nextIndex"]
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
class Mai2ServerConfig():
class Mai2ServerConfig:
def __init__(self, parent: "Mai2Config") -> None:
self.__config = parent
@property
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
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):
def __init__(self) -> None:
self.server = Mai2ServerConfig(self)
self.server = Mai2ServerConfig(self)

View File

@ -1,4 +1,4 @@
class Mai2Constants():
class Mai2Constants:
GRADE = {
"D": 0,
"C": 1,
@ -13,22 +13,10 @@ class Mai2Constants():
"SS": 10,
"SS+": 11,
"SSS": 12,
"SSS+": 13
}
FC = {
"None": 0,
"FC": 1,
"FC+": 2,
"AP": 3,
"AP+": 4
}
SYNC = {
"None": 0,
"FS": 1,
"FS+": 2,
"FDX": 3,
"FDX+": 4
"SSS+": 13,
}
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"
@ -43,9 +31,15 @@ class Mai2Constants():
VER_MAIMAI_DX_UNIVERSE = 4
VER_MAIMAI_DX_UNIVERSE_PLUS = 5
VERSION_STRING = ("maimai Delux", "maimai Delux PLUS", "maimai Delux Splash", "maimai Delux Splash PLUS", "maimai Delux Universe",
"maimai Delux Universe PLUS")
VERSION_STRING = (
"maimai Delux",
"maimai Delux PLUS",
"maimai Delux Splash",
"maimai Delux Splash PLUS",
"maimai Delux Universe",
"maimai Delux Universe PLUS",
)
@classmethod
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.config import CoreConfig
from titles.mai2.schema import Mai2ItemData, Mai2ProfileData, Mai2StaticData, Mai2ScoreData
from titles.mai2.schema import (
Mai2ItemData,
Mai2ProfileData,
Mai2StaticData,
Mai2ScoreData,
)
class Mai2Data(Data):
def __init__(self, cfg: CoreConfig) -> None:
@ -9,4 +15,4 @@ class Mai2Data(Data):
self.profile = Mai2ProfileData(self.config, self.session)
self.item = Mai2ItemData(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
class Mai2Servlet():
class Mai2Servlet:
def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None:
self.core_cfg = core_cfg
self.game_cfg = Mai2Config()
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 = [
Mai2Base(core_cfg, self.game_cfg),
@ -39,34 +41,52 @@ class Mai2Servlet():
self.logger = logging.getLogger("mai2")
log_fmt_str = "[%(asctime)s] Mai2 | %(levelname)s | %(message)s"
log_fmt = logging.Formatter(log_fmt_str)
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.core_cfg.server.log_dir, "mai2"), encoding='utf8',
when="d", backupCount=10)
fileHandler = TimedRotatingFileHandler(
"{0}/{1}.log".format(self.core_cfg.server.log_dir, "mai2"),
encoding="utf8",
when="d",
backupCount=10,
)
fileHandler.setFormatter(log_fmt)
consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt)
self.logger.addHandler(fileHandler)
self.logger.addHandler(consoleHandler)
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
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()
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:
return (False, "", "")
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, f"http://{core_cfg.title.hostname}/{game_code}/$v/", f"{core_cfg.title.hostname}/")
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,
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:
if url_path.lower() == "/ping":
@ -78,34 +98,36 @@ class Mai2Servlet():
internal_ver = 0
endpoint = url_split[len(url_split) - 1]
if version < 105: # 1.0
if version < 105: # 1.0
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
elif version >= 110 and version < 115: # Splash
elif version >= 110 and version < 115: # 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
elif version >= 120 and version < 125: # Universe
elif version >= 120 and version < 125: # 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
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
# doing encrypted. The likelyhood of false positives is low but
# 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
# technically not 0
self.logger.error("Encryption not supported at this time")
try:
try:
unzip = zlib.decompress(req_raw)
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"}')
req_data = json.loads(unzip)
self.logger.info(f"v{version} {endpoint} request - {req_data}")
func_to_find = "handle_" + inflection.underscore(endpoint) + "_request"
@ -121,10 +143,10 @@ class Mai2Servlet():
except Exception as e:
self.logger.error(f"Error handling v{version} method {endpoint} - {e}")
return zlib.compress(b'{"stat": "0"}')
if resp == None:
resp = {'returnCode': 1}
resp = {"returnCode": 1}
self.logger.info(f"Response {resp}")
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.const import Mai2Constants
class Mai2Plus(Mai2Base):
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
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.database import Mai2Data
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)
self.data = Mai2Data(config)
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:
self.logger.error(f"Invalid maidx version {version}")
exit(1)
def read(self) -> None:
data_dirs = []
if self.bin_dir is not None:
data_dirs += self.get_data_directories(self.bin_dir)
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:
self.logger.info(f"Read from {dir}")
self.get_events(f"{dir}/event")
@ -43,47 +53,64 @@ class Mai2Reader(BaseReader):
for dir in dirs:
if os.path.exists(f"{root}/{dir}/Event.xml"):
with open(f"{root}/{dir}/Event.xml", encoding="utf-8") as f:
troot = ET.fromstring(f.read())
name = troot.find('name').find('str').text
id = int(troot.find('name').find('id').text)
event_type = int(troot.find('infoType').text)
name = troot.find("name").find("str").text
id = int(troot.find("name").find("id").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}...")
def read_music(self, base_dir: str) -> None:
self.logger.info(f"Reading music from {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"):
with open(f"{root}/{dir}/Music.xml", encoding="utf-8") as f:
troot = ET.fromstring(f.read())
song_id = int(troot.find('name').find('id').text)
title = troot.find('name').find('str').text
artist = troot.find('artistName').find('str').text
genre = troot.find('genreName').find('str').text
bpm = int(troot.find('bpm').text)
added_ver = troot.find('AddVersion').find('str').text
song_id = int(troot.find("name").find("id").text)
title = troot.find("name").find("str").text
artist = troot.find("artistName").find("str").text
genre = troot.find("genreName").find("str").text
bpm = int(troot.find("bpm").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:
path = dif.find('file').find('path').text
path = dif.find("file").find("path").text
if path is not None:
if os.path.exists(f"{root}/{dir}/{path}"):
chart_id = int(path.split(".")[0].split('_')[1])
diff_num = float(f"{dif.find('level').text}.{dif.find('levelDecimal').text}")
note_designer = dif.find('notesDesigner').find('str').text
chart_id = int(path.split(".")[0].split("_")[1])
diff_num = float(
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:
self.logger.info(f"Reading tickets from {base_dir}...")
@ -91,13 +118,14 @@ class Mai2Reader(BaseReader):
for dir in dirs:
if os.path.exists(f"{root}/{dir}/Ticket.xml"):
with open(f"{root}/{dir}/Ticket.xml", encoding="utf-8") as f:
troot = ET.fromstring(f.read())
name = troot.find('name').find('str').text
id = int(troot.find('name').find('id').text)
ticket_type = int(troot.find('ticketKind').find('id').text)
price = int(troot.find('creditNum').text)
name = troot.find("name").find("str").text
id = int(troot.find("name").find("id").text)
ticket_type = int(troot.find("ticketKind").find("id").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}...")

View File

@ -3,4 +3,4 @@ from titles.mai2.schema.item import Mai2ItemData
from titles.mai2.schema.static import Mai2StaticData
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",
metadata,
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("level", Integer, nullable=False, server_default="1"),
Column("awakening", Integer, nullable=False, server_default="0"),
Column("use_count", Integer, nullable=False, server_default="0"),
UniqueConstraint("user", "character_id", name="mai2_item_character_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
card = Table(
"mai2_item_card",
metadata,
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_id", Integer, nullable=False),
Column("chara_id", Integer, nullable=False),
@ -33,54 +41,70 @@ card = Table(
Column("start_date", String(255), nullable=False),
Column("end_date", String(255), nullable=False),
UniqueConstraint("user", "card_kind", "card_id", name="mai2_item_card_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
item = Table(
"mai2_item_item",
metadata,
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_id", Integer, nullable=False),
Column("stock", Integer, 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"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
map = Table(
"mai2_item_map",
metadata,
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("distance", Integer, nullable=False),
Column("is_lock", Boolean, nullable=False, server_default="0"),
Column("is_clear", Boolean, nullable=False, server_default="0"),
Column("is_complete", Boolean, nullable=False, server_default="0"),
UniqueConstraint("user", "map_id", name="mai2_item_map_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
login_bonus = Table(
"mai2_item_login_bonus",
metadata,
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("point", Integer, nullable=False),
Column("is_current", 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"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
friend_season_ranking = Table(
"mai2_item_friend_season_ranking",
metadata,
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("point", Integer, nullable=False),
Column("rank", Integer, nullable=False),
@ -88,35 +112,46 @@ friend_season_ranking = Table(
Column("user_name", String(8), nullable=False),
Column("record_date", String(255), nullable=False),
UniqueConstraint("user", "season_id", "user_name", name="mai2_item_login_bonus_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
favorite = Table(
"mai2_item_favorite",
metadata,
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("itemIdList", JSON),
UniqueConstraint("user", "itemKind", name="mai2_item_favorite_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
charge = Table(
"mai2_item_charge",
metadata,
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("stock", Integer, nullable=False),
Column("purchase_date", String(255), nullable=False),
Column("valid_date", String(255), nullable=False),
UniqueConstraint("user", "charge_id", name="mai2_item_charge_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
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(
user=user_id,
item_kind=item_kind,
@ -132,28 +167,47 @@ class Mai2ItemData(BaseData):
result = self.execute(conflict)
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 result.lastrowid
def get_items(self, user_id: int, item_kind: int = None) -> Optional[List[Row]]:
if item_kind is None:
sql = item.select(item.c.user == user_id)
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)
if result is None: return None
if result is None:
return None
return result.fetchall()
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)
if result is None: return None
if result is None:
return None
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(
user=user_id,
bonus_id=bonus_id,
@ -170,25 +224,39 @@ class Mai2ItemData(BaseData):
result = self.execute(conflict)
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 result.lastrowid
def get_login_bonuses(self, user_id: int) -> Optional[List[Row]]:
sql = login_bonus.select(login_bonus.c.user == user_id)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
return result.fetchall()
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)
if result is None: return None
if result is None:
return None
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(
user=user_id,
map_id=map_id,
@ -207,25 +275,36 @@ class Mai2ItemData(BaseData):
result = self.execute(conflict)
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 result.lastrowid
def get_maps(self, user_id: int) -> Optional[List[Row]]:
sql = map.select(map.c.user == user_id)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
return result.fetchall()
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))
result = self.execute(sql)
if result is None: return None
if result is None:
return None
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(
user=user_id,
character_id=character_id,
@ -242,57 +321,64 @@ class Mai2ItemData(BaseData):
result = self.execute(conflict)
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 result.lastrowid
def get_characters(self, user_id: int) -> Optional[List[Row]]:
sql = character.select(character.c.user == user_id)
result = self.execute(sql)
if result is None: return None
if result is None:
return None
return result.fetchall()
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)
if result is None: return None
if result is None:
return None
return result.fetchone()
def get_friend_season_ranking(self, user_id: int) -> Optional[Row]:
sql = friend_season_ranking.select(friend_season_ranking.c.user == user_id)
result = self.execute(sql)
if result is None:return None
if result is None:
return None
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(
user=user_id,
kind=kind,
item_id_list=item_id_list
user=user_id, kind=kind, item_id_list=item_id_list
)
conflict = sql.on_duplicate_key_update(
item_id_list=item_id_list
)
conflict = sql.on_duplicate_key_update(item_id_list=item_id_list)
result = self.execute(conflict)
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 result.lastrowid
def get_favorites(self, user_id: int, kind: int = None) -> Optional[Row]:
if kind is None:
sql = favorite.select(favorite.c.user == user_id)
else:
sql = favorite.select(and_(
favorite.c.user == user_id,
favorite.c.itemKind == kind
))
sql = favorite.select(
and_(favorite.c.user == user_id, favorite.c.itemKind == kind)
)
result = self.execute(sql)
if result is None:return None
return result.fetchall()
if result is None:
return None
return result.fetchall()

View File

@ -14,7 +14,11 @@ detail = Table(
"mai2_profile_detail",
metadata,
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("userName", String(25)),
Column("isNetMember", Integer),
@ -41,9 +45,9 @@ detail = Table(
Column("lastRomVersion", String(25)),
Column("lastDataVersion", 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("lastTrialPlayDate", String(25)), # new with uni+
Column("lastTrialPlayDate", String(25)), # new with uni+
Column("lastPlayCredit", Integer),
Column("lastPlayMode", Integer),
Column("lastPlaceId", Integer),
@ -90,16 +94,20 @@ detail = Table(
Column("playerOldRating", BigInteger),
Column("playerNewRating", BigInteger),
Column("dateTime", BigInteger),
Column("banState", Integer), # new with uni+
Column("banState", Integer), # new with uni+
UniqueConstraint("user", "version", name="mai2_profile_detail_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
ghost = Table(
"mai2_profile_ghost",
metadata,
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("name", String(25)),
Column("iconId", Integer),
@ -120,15 +128,21 @@ ghost = Table(
Column("resultBitList", JSON),
Column("resultNum", Integer),
Column("achievement", Integer),
UniqueConstraint("user", "version", "musicId", "difficulty", name="mai2_profile_ghost_uk"),
mysql_charset='utf8mb4'
UniqueConstraint(
"user", "version", "musicId", "difficulty", name="mai2_profile_ghost_uk"
),
mysql_charset="utf8mb4",
)
extend = Table(
"mai2_profile_extend",
metadata,
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("selectMusicId", Integer),
Column("selectDifficultyId", Integer),
@ -145,14 +159,18 @@ extend = Table(
Column("selectedCardList", JSON),
Column("encountMapNpcList", JSON),
UniqueConstraint("user", "version", name="mai2_profile_extend_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
option = Table(
"mai2_profile_option",
metadata,
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("selectMusicId", Integer),
Column("optionKind", Integer),
@ -200,14 +218,18 @@ option = Table(
Column("sortTab", Integer),
Column("sortMusic", Integer),
UniqueConstraint("user", "version", name="mai2_profile_option_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
rating = Table(
"mai2_profile_rating",
metadata,
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("rating", Integer),
Column("ratingList", JSON),
@ -216,26 +238,34 @@ rating = Table(
Column("nextNewRatingList", JSON),
Column("udemae", JSON),
UniqueConstraint("user", "version", name="mai2_profile_rating_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
region = Table(
"mai2_profile_region",
metadata,
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("playCount", Integer, server_default="1"),
Column("created", String(25)),
UniqueConstraint("user", "regionId", name="mai2_profile_region_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
activity = Table(
"mai2_profile_activity",
metadata,
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("activityId", Integer, nullable=False),
Column("param1", Integer, nullable=False),
@ -244,11 +274,14 @@ activity = Table(
Column("param4", Integer, nullable=False),
Column("sortNumber", Integer, nullable=False),
UniqueConstraint("user", "kind", "activityId", name="mai2_profile_activity_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
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["version"] = version
sql = insert(detail).values(**detail_data)
@ -257,18 +290,25 @@ class Mai2ProfileData(BaseData):
result = self.execute(conflict)
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 result.lastrowid
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)
if result is None:return None
if result is None:
return None
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["version_int"] = version
@ -282,13 +322,18 @@ class Mai2ProfileData(BaseData):
return result.lastrowid
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)
if result is None:return None
if result is None:
return None
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["version"] = version
@ -302,13 +347,18 @@ class Mai2ProfileData(BaseData):
return result.lastrowid
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)
if result is None:return None
if result is None:
return None
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["version"] = version
@ -322,13 +372,18 @@ class Mai2ProfileData(BaseData):
return result.lastrowid
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)
if result is None:return None
if result is None:
return None
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["version"] = version
@ -342,23 +397,24 @@ class Mai2ProfileData(BaseData):
return result.lastrowid
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)
if result is None:return None
if result is None:
return None
return result.fetchone()
def put_profile_region(self, user_id: int, region_id: int) -> Optional[int]:
sql = insert(region).values(
user = user_id,
regionId = region_id,
created = datetime.strftime(datetime.now(), Mai2Constants.DATE_TIME_FORMAT)
)
conflict = sql.on_duplicate_key_update(
playCount = region.c.playCount + 1
user=user_id,
regionId=region_id,
created=datetime.strftime(datetime.now(), Mai2Constants.DATE_TIME_FORMAT),
)
conflict = sql.on_duplicate_key_update(playCount=region.c.playCount + 1)
result = self.execute(conflict)
if result is None:
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)
result = self.execute(sql)
if result is None:return None
if result is None:
return None
return result.fetchall()
def put_profile_activity(self, user_id: int, activity_data: Dict) -> Optional[int]:
if "id" in activity_data:
activity_data["activityId"] = activity_data["id"]
activity_data.pop("id")
activity_data["user"] = user_id
sql = insert(activity).values(**activity_data)
conflict = sql.on_duplicate_key_update(**activity_data)
result = self.execute(conflict)
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 result.lastrowid
def get_profile_activity(self, user_id: int, kind: int = None) -> Optional[Row]:
sql = activity.select(
and_(
activity.c.user == user_id,
activity.c.user == user_id,
(activity.c.kind == kind) if kind is not None else True,
)
)
result = self.execute(sql)
if result is None:return None
if result is None:
return None
return result.fetchone()

View File

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

View File

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

View File

@ -8,7 +8,8 @@ from titles.mai2.base import Mai2Base
from titles.mai2.config import Mai2Config
from titles.mai2.const import Mai2Constants
class Mai2Splash(Mai2Base):
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
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.const import Mai2Constants
class Mai2SplashPlus(Mai2Base):
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
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.config import Mai2Config
class Mai2Universe(Mai2Base):
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
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.config import Mai2Config
class Mai2UniversePlus(Mai2Base):
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
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
reader = OngekiReader
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.config import OngekiConfig
class OngekiBattleGrade(Enum):
FAILED = 0
DRAW = 1
@ -21,6 +22,7 @@ class OngekiBattleGrade(Enum):
UNBELIEVABLE_GOLD = 6
UNBELIEVABLE_RAINBOW = 7
class OngekiBattlePointGrade(Enum):
FRESHMAN = 0
KYU10 = 1
@ -45,20 +47,22 @@ class OngekiBattlePointGrade(Enum):
DAN10 = 20
SODEN = 21
class OngekiTechnicalGrade(Enum):
D = 0
C = 1
B = 2
BB = 3
BBB = 4
A = 5
AA = 6
AAA = 7
S = 8
SS = 9
SSS = 10
D = 0
C = 1
B = 2
BB = 3
BBB = 4
A = 5
AA = 6
AAA = 7
S = 8
SS = 9
SSS = 10
SSSp = 11
class OngekiDifficulty(Enum):
BASIC = 0
ADVANCED = 1
@ -66,6 +70,7 @@ class OngekiDifficulty(Enum):
MASTER = 3
LUNATIC = 10
class OngekiGPLogKind(Enum):
NONE = 0
BUY1_START = 1
@ -82,22 +87,28 @@ class OngekiGPLogKind(Enum):
PAY_MAS_UNLOCK = 13
PAY_MONEY = 14
class OngekiBase():
class OngekiBase:
def __init__(self, core_cfg: CoreConfig, game_cfg: OngekiConfig) -> None:
self.core_cfg = core_cfg
self.game_cfg = game_cfg
self.data = OngekiData(core_cfg)
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.logger = logging.getLogger("ongeki")
self.game = OngekiConstants.GAME_CODE
self.version = OngekiConstants.VER_ONGEKI
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_end = date.strftime(datetime.now() + timedelta(hours=4), self.date_time_format)
reboot_start = date.strftime(
datetime.now() + timedelta(hours=3), self.date_time_format
)
reboot_end = date.strftime(
datetime.now() + timedelta(hours=4), self.date_time_format
)
return {
"gameSetting": {
"dataVersion": "1.00.00",
@ -128,20 +139,53 @@ class OngekiBase():
def handle_get_game_ranking_api_request(self, data: Dict) -> Dict:
return {"length": 0, "gameRankingList": []}
def handle_get_game_point_api_request(self, data: Dict) -> Dict:
"""
Sets the GP ammount for A and B sets for 1 - 3 crdits
"""
return {"length":6,"gamePointList":[
{"type":0,"cost":100,"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"}
]}
return {
"length": 6,
"gamePointList": [
{
"type": 0,
"cost": 100,
"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:
return {"returnCode": 1, "apiName": "gameLogin"}
@ -184,11 +228,19 @@ class OngekiBase():
def handle_upsert_user_gplog_api_request(self, data: Dict) -> Dict:
user = data["userId"]
if user >= 200000000000000: # Account for guest play
if user >= 200000000000000: # Account for guest play
user = None
self.data.log.put_gp_log(user, data["usedCredit"], data["placeName"], data["userGplog"]["trxnDate"],
data["userGplog"]["placeId"], data["userGplog"]["kind"], data["userGplog"]["pattern"], data["userGplog"]["currentGP"])
self.data.log.put_gp_log(
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"}
@ -197,39 +249,53 @@ class OngekiBase():
def handle_get_game_event_api_request(self, data: Dict) -> Dict:
evts = self.data.static.get_enabled_events(self.version)
evt_list = []
for event in evts:
evt_list.append({
"type": event["type"],
"id": event["eventId"],
"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}
evt_list.append(
{
"type": event["type"],
"id": event["eventId"],
"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,
}
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:
for i in range(1,231):
for i in range(1, 231):
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:
for i in range(8000,8051):
for i in range(8000, 8051):
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:
return {"userId": data["userId"], "length": 0, "userRegionList": []}
def handle_get_user_preview_api_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile_preview(data["userId"], self.version)
if profile is None:
if profile is None:
return {
"userId": data["userId"],
"userId": data["userId"],
"isLogin": False,
"lastLoginDate": "0000-00-00 00:00:00",
"userName": "",
@ -240,12 +306,12 @@ class OngekiBase():
"lastGameId": "",
"lastRomVersion": "",
"lastDataVersion": "",
"lastPlayDate": "",
"lastPlayDate": "",
"nameplateId": 0,
"trophyId": 0,
"cardId": 0,
"dispPlayerLv": 0,
"dispRating": 0,
"trophyId": 0,
"cardId": 0,
"dispPlayerLv": 0,
"dispRating": 0,
"dispBP": 0,
"headphone": 0,
"banStatus": 0,
@ -253,7 +319,7 @@ class OngekiBase():
}
return {
"userId": data["userId"],
"userId": data["userId"],
"isLogin": False,
"lastLoginDate": profile["lastPlayDate"],
"userName": profile["userName"],
@ -264,12 +330,12 @@ class OngekiBase():
"lastGameId": profile["lastGameId"],
"lastRomVersion": profile["lastRomVersion"],
"lastDataVersion": profile["lastDataVersion"],
"lastPlayDate": profile["lastPlayDate"],
"lastPlayDate": profile["lastPlayDate"],
"nameplateId": profile["nameplateId"],
"trophyId": profile["trophyId"],
"cardId": profile["cardId"],
"dispPlayerLv": profile["dispPlayerLv"],
"dispRating": profile["dispRating"],
"trophyId": profile["trophyId"],
"cardId": profile["cardId"],
"dispPlayerLv": profile["dispPlayerLv"],
"dispRating": profile["dispRating"],
"dispBP": profile["dispBP"],
"headphone": profile["headphone"],
"banStatus": profile["banStatus"],
@ -297,7 +363,8 @@ class OngekiBase():
def handle_get_user_tech_event_api_request(self, data: Dict) -> Dict:
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 = []
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:
#user_event_ranking_list = self.data.item.get_tech_event_ranking(data["userId"])
#if user_event_ranking_list is None: return {}
# user_event_ranking_list = self.data.item.get_tech_event_ranking(data["userId"])
# if user_event_ranking_list is None: return {}
evt_ranking = []
#for evt in user_event_ranking_list:
# for evt in user_event_ranking_list:
# tmp = evt._asdict()
# tmp.pop("id")
# tmp.pop("user")
@ -331,7 +398,8 @@ class OngekiBase():
def handle_get_user_kop_api_request(self, data: Dict) -> Dict:
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:
kop.pop("user")
@ -349,10 +417,10 @@ class OngekiBase():
next_idx = data["nextIndex"]
start_idx = next_idx
end_idx = max_ct + start_idx
if len(song_list[start_idx:]) > max_ct:
next_idx += max_ct
else:
next_idx = -1
@ -360,15 +428,20 @@ class OngekiBase():
"userId": data["userId"],
"length": len(song_list[start_idx:end_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:
kind = data["nextIndex"] / 10000000000
p = self.data.item.get_items(data["userId"], kind)
if p is None:
return {"userId": data["userId"], "nextIndex": -1, "itemKind": kind, "userItemList": []}
if p is None:
return {
"userId": data["userId"],
"nextIndex": -1,
"itemKind": kind,
"userItemList": [],
}
items: list[Dict[str, Any]] = []
for i in range(data["nextIndex"] % 10000000000, len(p)):
@ -381,14 +454,23 @@ class OngekiBase():
xout = kind * 10000000000 + (data["nextIndex"] % 10000000000) + len(items)
if len(items) < data["maxCount"] or data["maxCount"] == 0: nextIndex = 0
else: nextIndex = xout
if len(items) < data["maxCount"] or data["maxCount"] == 0:
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:
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
user_opts = o._asdict()
@ -401,14 +483,17 @@ class OngekiBase():
def handle_get_user_data_api_request(self, data: Dict) -> Dict:
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"])
if cards is None or len(cards) == 0:
# 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 {}
# get the dict representation of the row so we can modify values
user_data = p._asdict()
@ -422,14 +507,14 @@ class OngekiBase():
# add access code that we don't store
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:
#user_event_ranking_list = self.data.item.get_event_ranking(data["userId"])
#if user_event_ranking_list is None: return {}
# user_event_ranking_list = self.data.item.get_event_ranking(data["userId"])
# if user_event_ranking_list is None: return {}
evt_ranking = []
#for evt in user_event_ranking_list:
# for evt in user_event_ranking_list:
# tmp = evt._asdict()
# tmp.pop("id")
# tmp.pop("user")
@ -443,7 +528,8 @@ class OngekiBase():
def handle_get_user_login_bonus_api_request(self, data: Dict) -> Dict:
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 = []
for scenerio in user_login_bonus_list:
@ -451,16 +537,19 @@ class OngekiBase():
tmp.pop("id")
tmp.pop("user")
login_bonuses.append(tmp)
return {
"userId": data["userId"],
"length": len(login_bonuses),
"userLoginBonusList": login_bonuses
"userId": data["userId"],
"length": len(login_bonuses),
"userLoginBonusList": login_bonuses,
}
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"])
if p is None: return {}
p = self.data.profile.get_profile(
self.game, self.version, user_id=data["userId"]
)
if p is None:
return {}
profile = json.loads(p["data"])
return {
"userId": data["userId"],
@ -470,7 +559,8 @@ class OngekiBase():
def handle_get_user_recent_rating_api_request(self, data: Dict) -> Dict:
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"]
@ -482,31 +572,35 @@ class OngekiBase():
def handle_get_user_activity_api_request(self, data: Dict) -> Dict:
activity = self.data.profile.get_profile_activity(data["userId"], data["kind"])
if activity is None: return {}
if activity is None:
return {}
user_activity = []
for act in activity:
user_activity.append({
"kind": act["kind"],
"id": act["activityId"],
"sortNumber": act["sortNumber"],
"param1": act["param1"],
"param2": act["param2"],
"param3": act["param3"],
"param4": act["param4"],
})
user_activity.append(
{
"kind": act["kind"],
"id": act["activityId"],
"sortNumber": act["sortNumber"],
"param1": act["param1"],
"param2": act["param2"],
"param3": act["param3"],
"param4": act["param4"],
}
)
return {
"userId": data["userId"],
"userId": data["userId"],
"length": len(user_activity),
"kind": data["kind"],
"userActivityList": user_activity
"userActivityList": user_activity,
}
def handle_get_user_story_api_request(self, data: Dict) -> Dict:
user_stories = self.data.item.get_stories(data["userId"])
if user_stories is None: return {}
if user_stories is None:
return {}
story_list = []
for story in user_stories:
@ -516,14 +610,15 @@ class OngekiBase():
story_list.append(tmp)
return {
"userId": data["userId"],
"length": len(story_list),
"userStoryList": story_list
"userId": data["userId"],
"length": len(story_list),
"userStoryList": story_list,
}
def handle_get_user_chapter_api_request(self, data: Dict) -> Dict:
user_chapters = self.data.item.get_chapters(data["userId"])
if user_chapters is None: return {}
if user_chapters is None:
return {}
chapter_list = []
for chapter in user_chapters:
@ -531,11 +626,11 @@ class OngekiBase():
tmp.pop("id")
tmp.pop("user")
chapter_list.append(tmp)
return {
"userId": data["userId"],
"length": len(chapter_list),
"userChapterList": chapter_list
"userId": data["userId"],
"length": len(chapter_list),
"userChapterList": chapter_list,
}
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:
user_characters = self.data.item.get_characters(data["userId"])
if user_characters is None: return {}
if user_characters is None:
return {}
character_list = []
for character in user_characters:
@ -555,16 +651,17 @@ class OngekiBase():
tmp.pop("id")
tmp.pop("user")
character_list.append(tmp)
return {
"userId": data["userId"],
"length": len(character_list),
"userCharacterList": character_list
"userId": data["userId"],
"length": len(character_list),
"userCharacterList": character_list,
}
def handle_get_user_card_api_request(self, data: Dict) -> Dict:
user_cards = self.data.item.get_cards(data["userId"])
if user_cards is None: return {}
if user_cards is None:
return {}
card_list = []
for card in user_cards:
@ -572,17 +669,18 @@ class OngekiBase():
tmp.pop("id")
tmp.pop("user")
card_list.append(tmp)
return {
"userId": data["userId"],
"length": len(card_list),
"userCardList": card_list
"userId": data["userId"],
"length": len(card_list),
"userCardList": card_list,
}
def handle_get_user_deck_by_key_api_request(self, data: Dict) -> Dict:
# Auth key doesn't matter, it just wants all the decks
decks = self.data.item.get_decks(data["userId"])
if decks is None: return {}
if decks is None:
return {}
deck_list = []
for deck in decks:
@ -599,7 +697,8 @@ class OngekiBase():
def handle_get_user_trade_item_api_request(self, data: Dict) -> Dict:
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 = []
for trade_item in user_trade_items:
@ -616,7 +715,8 @@ class OngekiBase():
def handle_get_user_scenario_api_request(self, data: Dict) -> Dict:
user_scenerio = self.data.item.get_scenerios(data["userId"])
if user_scenerio is None: return {}
if user_scenerio is None:
return {}
scenerio_list = []
for scenerio in user_scenerio:
@ -624,7 +724,7 @@ class OngekiBase():
tmp.pop("id")
tmp.pop("user")
scenerio_list.append(tmp)
return {
"userId": data["userId"],
"length": len(scenerio_list),
@ -633,7 +733,8 @@ class OngekiBase():
def handle_get_user_ratinglog_api_request(self, data: Dict) -> Dict:
rating_log = self.data.profile.get_profile_rating_log(data["userId"])
if rating_log is None: return {}
if rating_log is None:
return {}
userRatinglogList = []
for rating in rating_log:
@ -650,7 +751,8 @@ class OngekiBase():
def handle_get_user_mission_point_api_request(self, data: Dict) -> Dict:
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 = []
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:
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 = []
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:
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 = []
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:
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 = []
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:
p = self.data.item.get_bosses(data["userId"])
if p is None: return {}
if p is None:
return {}
boss_list = []
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.
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:
self.data.profile.put_profile_options(user_id, upsert["userOption"][0])
@ -751,27 +859,37 @@ class OngekiBase():
if "userActivityList" in upsert:
for act in upsert["userActivityList"]:
self.data.profile.put_profile_activity(user_id, act["kind"], act["id"], act["sortNumber"], act["param1"],
act["param2"], act["param3"], act["param4"])
self.data.profile.put_profile_activity(
user_id,
act["kind"],
act["id"],
act["sortNumber"],
act["param1"],
act["param2"],
act["param3"],
act["param4"],
)
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:
self.data.profile.put_profile_bp_list(user_id, upsert["userBpBaseList"])
if "userMusicDetailList" in upsert:
for x in upsert["userMusicDetailList"]:
self.data.score.put_best_score(user_id, x)
if "userCharacterList" in upsert:
for x in upsert["userCharacterList"]:
self.data.item.put_character(user_id, x)
if "userCardList" in upsert:
for x in upsert["userCardList"]:
self.data.item.put_card(user_id, x)
if "userDeckList" in upsert:
for x in upsert["userDeckList"]:
self.data.item.put_deck(user_id, x)
@ -779,43 +897,45 @@ class OngekiBase():
if "userTrainingRoomList" in upsert:
for x in upsert["userTrainingRoomList"]:
self.data.profile.put_training_room(user_id, x)
if "userStoryList" in upsert:
for x in upsert["userStoryList"]:
self.data.item.put_story(user_id, x)
if "userChapterList" in upsert:
for x in upsert["userChapterList"]:
self.data.item.put_chapter(user_id, x)
if "userMemoryChapterList" in upsert:
for x in upsert["userMemoryChapterList"]:
self.data.item.put_memorychapter(user_id, x)
if "userItemList" in upsert:
for x in upsert["userItemList"]:
self.data.item.put_item(user_id, x)
if "userMusicItemList" in upsert:
for x in upsert["userMusicItemList"]:
self.data.item.put_music_item(user_id, x)
if "userLoginBonusList" in upsert:
for x in upsert["userLoginBonusList"]:
self.data.item.put_login_bonus(user_id, x)
if "userEventPointList" in upsert:
for x in upsert["userEventPointList"]:
self.data.item.put_event_point(user_id, x)
if "userMissionPointList" in upsert:
for x in upsert["userMissionPointList"]:
self.data.item.put_mission_point(user_id, x)
if "userRatinglogList" in upsert:
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:
for x in upsert["userBossList"]:
self.data.item.put_boss(user_id, x)
@ -844,7 +964,7 @@ class OngekiBase():
for x in upsert["userKopList"]:
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:
"""
@ -857,29 +977,28 @@ class OngekiBase():
"length": 0,
"userRivalList": [],
}
return {
"userId": data["userId"],
"length": len(rival_list),
"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
"""
rivals = []
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:
continue
rivals.append({
"rivalUserId": rival["rival"],
"rivalUserName": name
})
rivals.append({"rivalUserId": rival["rival"], "rivalUserName": name})
return {
"userId": data["userId"],
"length": len(rivals),
@ -893,11 +1012,9 @@ class OngekiBase():
rival_id = data["rivalUserId"]
next_idx = data["nextIndex"]
max_ct = data["maxCount"]
music = self.handle_get_user_music_api_request({
"userId": rival_id,
"nextIndex": next_idx,
"maxCount": max_ct
})
music = self.handle_get_user_music_api_request(
{"userId": rival_id, "nextIndex": next_idx, "maxCount": max_ct}
)
for song in music["userMusicList"]:
song["userRivalMusicDetailList"] = song["userMusicDetailList"]
@ -921,18 +1038,15 @@ class OngekiBase():
tmp = md._asdict()
tmp.pop("user")
tmp.pop("id")
for song in song_list:
if song["userMusicDetailList"][0]["musicId"] == tmp["musicId"]:
found = True
song["userMusicDetailList"].append(tmp)
song["length"] = len(song["userMusicDetailList"])
break
if not found:
song_list.append({
"length": 1,
"userMusicDetailList": [tmp]
})
song_list.append({"length": 1, "userMusicDetailList": [tmp]})
return song_list

View File

@ -31,7 +31,8 @@ class OngekiBright(OngekiBase):
if cards is None or len(cards) == 0:
# This should never happen
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 {}
# get the dict representation of the row so we can modify values
@ -86,7 +87,7 @@ class OngekiBright(OngekiBase):
"userId": data["userId"],
"length": len(card_list[start_idx:end_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:
@ -115,31 +116,26 @@ class OngekiBright(OngekiBase):
"userId": data["userId"],
"length": len(character_list[start_idx:end_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:
user_gachas = self.data.item.get_user_gachas(data["userId"])
if user_gachas is None:
return {
"userId": data["userId"],
"length": 0,
"userGachaList": []
}
return {"userId": data["userId"], "length": 0, "userGachaList": []}
user_gacha_list = []
for gacha in user_gachas:
tmp = gacha._asdict()
tmp.pop("id")
tmp.pop("user")
tmp["dailyGachaDate"] = datetime.strftime(
tmp["dailyGachaDate"], "%Y-%m-%d")
tmp["dailyGachaDate"] = datetime.strftime(tmp["dailyGachaDate"], "%Y-%m-%d")
user_gacha_list.append(tmp)
return {
"userId": data["userId"],
"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:
@ -147,21 +143,16 @@ class OngekiBright(OngekiBase):
def handle_cm_get_user_gacha_supply_api_request(self, data: Dict) -> Dict:
# not used for now? not sure what it even does
user_gacha_supplies = self.data.item.get_user_gacha_supplies(
data["userId"])
user_gacha_supplies = self.data.item.get_user_gacha_supplies(data["userId"])
if user_gacha_supplies is None:
return {
"supplyId": 1,
"length": 0,
"supplyCardList": []
}
return {"supplyId": 1, "length": 0, "supplyCardList": []}
supply_list = [gacha["cardId"] for gacha in user_gacha_supplies]
return {
"supplyId": 1,
"length": len(supply_list),
"supplyCardList": supply_list
"supplyCardList": supply_list,
}
def handle_get_game_gacha_api_request(self, data: Dict) -> Dict:
@ -182,29 +173,33 @@ class OngekiBright(OngekiBase):
tmp = gacha._asdict()
tmp.pop("id")
tmp.pop("version")
tmp["startDate"] = datetime.strftime(
tmp["startDate"], "%Y-%m-%d %H:%M:%S")
tmp["endDate"] = datetime.strftime(
tmp["endDate"], "%Y-%m-%d %H:%M:%S")
tmp["startDate"] = datetime.strftime(tmp["startDate"], "%Y-%m-%d %H:%M:%S")
tmp["endDate"] = datetime.strftime(tmp["endDate"], "%Y-%m-%d %H:%M:%S")
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"], "%Y-%m-%d %H:%M:%S")
tmp["noticeEndDate"], "%Y-%m-%d %H:%M:%S"
)
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
# so only up to bright, 1140 is the first bright memory gacha
if self.version == OngekiConstants.VER_ONGEKI_BRIGHT_MEMORY:
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)
return {
"length": len(game_gacha_list),
"gameGachaList": game_gacha_list,
# no clue
"registIdList": []
"registIdList": [],
}
def handle_roll_gacha_api_request(self, data: Dict) -> Dict:
@ -251,7 +246,7 @@ class OngekiBright(OngekiBase):
assert len(rarity) == 100
# 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 book_used == 1:
@ -273,15 +268,9 @@ class OngekiBright(OngekiBase):
gacha_cards = self.data.static.get_gacha_cards(gacha_id)
for card in gacha_cards:
if card["rarity"] == 3:
cards_sr.append({
"cardId": card["cardId"],
"rarity": 2
})
cards_sr.append({"cardId": card["cardId"], "rarity": 2})
elif card["rarity"] == 4:
cards_ssr.append({
"cardId": card["cardId"],
"rarity": 3
})
cards_ssr.append({"cardId": card["cardId"], "rarity": 3})
else:
cards_sr = self.data.static.get_cards_by_rarity(self.version, 2)
cards_ssr = self.data.static.get_cards_by_rarity(self.version, 3)
@ -294,46 +283,39 @@ class OngekiBright(OngekiBase):
for card in gacha_cards:
# make sure to add the cards to the corresponding rarity
if card["rarity"] == 2:
cards_r += [{
"cardId": card["cardId"],
"rarity": 1
}] * chances
cards_r += [{"cardId": card["cardId"], "rarity": 1}] * chances
if card["rarity"] == 3:
cards_sr += [{
"cardId": card["cardId"],
"rarity": 2
}] * chances
cards_sr += [{"cardId": card["cardId"], "rarity": 2}] * chances
elif card["rarity"] == 4:
cards_ssr += [{
"cardId": card["cardId"],
"rarity": 3
}] * chances
cards_ssr += [{"cardId": card["cardId"], "rarity": 3}] * chances
# get the card id for each roll
rolled_cards = []
for i in range(len(rolls)):
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:
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:
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 = []
for card in rolled_cards:
game_gacha_card_list.append({
"gachaId": data["gachaId"],
"cardId": card["cardId"],
# +1 because Card Maker is weird
"rarity": card["rarity"] + 1,
"weight": 1,
"isPickup": False,
"isSelect": False
})
game_gacha_card_list.append(
{
"gachaId": data["gachaId"],
"cardId": card["cardId"],
# +1 because Card Maker is weird
"rarity": card["rarity"] + 1,
"weight": 1,
"isPickup": False,
"isSelect": False,
}
)
return {
"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):
@ -342,12 +324,12 @@ class OngekiBright(OngekiBase):
gacha_id = data["gachaId"]
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"]
total_gacha_count, ceiling_gacha_count = 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
user_gacha = self.data.item.get_user_gacha(user_id, gacha_id)
@ -374,9 +356,11 @@ class OngekiBright(OngekiBase):
selectPoint=select_point,
useSelectPoint=0,
dailyGachaCnt=daily_gacha_cnt + gacha_count,
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,
dailyGachaDate=daily_gacha_date
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,
dailyGachaDate=daily_gacha_date,
)
if "userData" in upsert and len(upsert["userData"]) > 0:
@ -385,11 +369,13 @@ class OngekiBright(OngekiBase):
if p is not None:
# save the bright memory profile
self.data.profile.put_profile_data(
user_id, self.version, upsert["userData"][0])
user_id, self.version, upsert["userData"][0]
)
else:
# save the bright profile
self.data.profile.put_profile_data(
user_id, self.version, upsert["userData"][0])
user_id, self.version, upsert["userData"][0]
)
if "userCharacterList" in upsert:
for x in upsert["userCharacterList"]:
@ -407,7 +393,7 @@ class OngekiBright(OngekiBase):
# if "gameGachaCardList" in upsert:
# 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:
upsert = data["cmUpsertUserSelectGacha"]
@ -419,11 +405,13 @@ class OngekiBright(OngekiBase):
if p is not None:
# save the bright memory profile
self.data.profile.put_profile_data(
user_id, self.version, upsert["userData"][0])
user_id, self.version, upsert["userData"][0]
)
else:
# save the bright profile
self.data.profile.put_profile_data(
user_id, self.version, upsert["userData"][0])
user_id, self.version, upsert["userData"][0]
)
if "userCharacterList" in upsert:
for x in upsert["userCharacterList"]:
@ -439,10 +427,10 @@ class OngekiBright(OngekiBase):
user_id,
x["gachaId"],
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:
game_gacha_cards = self.data.static.get_gacha_cards(data["gachaId"])
@ -459,7 +447,7 @@ class OngekiBright(OngekiBase):
"rarity": 4,
"weight": 1,
"isPickup": False,
"isSelect": True
"isSelect": True,
},
{
"gachaId": data["gachaId"],
@ -467,7 +455,7 @@ class OngekiBright(OngekiBase):
"rarity": 3,
"weight": 2,
"isPickup": False,
"isSelect": True
"isSelect": True,
},
{
"gachaId": data["gachaId"],
@ -475,7 +463,7 @@ class OngekiBright(OngekiBase):
"rarity": 3,
"weight": 2,
"isPickup": False,
"isSelect": True
"isSelect": True,
},
{
"gachaId": data["gachaId"],
@ -483,7 +471,7 @@ class OngekiBright(OngekiBase):
"rarity": 2,
"weight": 3,
"isPickup": False,
"isSelect": True
"isSelect": True,
},
{
"gachaId": data["gachaId"],
@ -491,7 +479,7 @@ class OngekiBright(OngekiBase):
"rarity": 2,
"weight": 3,
"isPickup": False,
"isSelect": True
"isSelect": True,
},
{
"gachaId": data["gachaId"],
@ -499,12 +487,12 @@ class OngekiBright(OngekiBase):
"rarity": 2,
"weight": 3,
"isPickup": False,
"isSelect": True
}
"isSelect": True,
},
],
"emissionList": [],
"afterCalcList": [],
"ssrBookCalcList": []
"ssrBookCalcList": [],
}
game_gacha_card_list = []
@ -521,7 +509,7 @@ class OngekiBright(OngekiBase):
# again no clue
"emissionList": [],
"afterCalcList": [],
"ssrBookCalcList": []
"ssrBookCalcList": [],
}
def handle_get_game_theater_api_request(self, data: Dict) -> Dict:
@ -548,18 +536,14 @@ class OngekiBright(OngekiBase):
}
"""
return {
"length": 0,
"gameTheaterList": [],
"registIdList": []
}
return {"length": 0, "gameTheaterList": [], "registIdList": []}
def handle_cm_upsert_user_print_playlog_api_request(self, data: Dict) -> Dict:
return {
"returnCode": 1,
"orderId": 0,
"serialId": "11111111111111111111",
"apiName": "CMUpsertUserPrintPlaylogApi"
"apiName": "CMUpsertUserPrintPlaylogApi",
}
def handle_cm_upsert_user_printlog_api_request(self, data: Dict) -> Dict:
@ -567,34 +551,32 @@ class OngekiBright(OngekiBase):
"returnCode": 1,
"orderId": 0,
"serialId": "11111111111111111111",
"apiName": "CMUpsertUserPrintlogApi"
"apiName": "CMUpsertUserPrintlogApi",
}
def handle_cm_upsert_user_print_api_request(self, data: Dict) -> Dict:
user_print_detail = data["userPrintDetail"]
# 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
user_print_detail.pop("orderId")
user_print_detail.pop("printNumber")
user_print_detail.pop("serialId")
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
self.data.item.put_user_print_detail(
data["userId"],
serial_id,
user_print_detail
data["userId"], serial_id, user_print_detail
)
return {
"returnCode": 1,
"serialId": serial_id,
"apiName": "CMUpsertUserPrintApi"
"apiName": "CMUpsertUserPrintApi",
}
def handle_cm_upsert_user_all_api_request(self, data: Dict) -> Dict:
@ -607,17 +589,26 @@ class OngekiBright(OngekiBase):
if p is not None:
# save the bright memory profile
self.data.profile.put_profile_data(
user_id, self.version, upsert["userData"][0])
user_id, self.version, upsert["userData"][0]
)
else:
# save the bright profile
self.data.profile.put_profile_data(
user_id, self.version, upsert["userData"][0])
user_id, self.version, upsert["userData"][0]
)
if "userActivityList" in upsert:
for act in upsert["userActivityList"]:
self.data.profile.put_profile_activity(
user_id, act["kind"], act["id"], act["sortNumber"],
act["param1"], act["param2"], act["param3"], act["param4"])
user_id,
act["kind"],
act["id"],
act["sortNumber"],
act["param1"],
act["param2"],
act["param3"],
act["param4"],
)
if "userItemList" in upsert:
for x in upsert["userItemList"]:
@ -627,4 +618,4 @@ class OngekiBright(OngekiBase):
for x in upsert["userCardList"]:
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:
memories = self.data.item.get_memorychapters(data["userId"])
if not memories:
return {"userId": data["userId"], "length":6, "userMemoryChapterList":[
{"gaugeId":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}
]}
return {
"userId": data["userId"],
"length": 6,
"userMemoryChapterList": [
{
"gaugeId": 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 = []
for chp in memories:
@ -49,14 +131,11 @@ class OngekiBrightMemory(OngekiBright):
return {
"userId": data["userId"],
"length": len(memory_chp),
"userMemoryChapterList": memory_chp
"userMemoryChapterList": memory_chp,
}
def handle_get_game_music_release_state_api_request(self, data: Dict) -> Dict:
return {
"techScore": 0,
"cardNum": 0
}
return {"techScore": 0, "cardNum": 0}
def handle_cm_get_user_data_api_request(self, data: Dict) -> Dict:
# check for a bright memory profile

View File

@ -3,26 +3,34 @@ from typing import List
from core.config import CoreConfig
class OngekiServerConfig():
class OngekiServerConfig:
def __init__(self, parent_config: "OngekiConfig") -> None:
self.__config = parent_config
@property
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
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:
self.__config = parent_config
@property
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):

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