Merge branch 'develop' into fork_develop
This commit is contained in:
+99
-36
@@ -8,17 +8,18 @@ from logging.handlers import TimedRotatingFileHandler
|
|||||||
from core.config import CoreConfig
|
from core.config import CoreConfig
|
||||||
from core.data import Data
|
from core.data import Data
|
||||||
|
|
||||||
|
|
||||||
class AimedbProtocol(Protocol):
|
class AimedbProtocol(Protocol):
|
||||||
AIMEDB_RESPONSE_CODES = {
|
AIMEDB_RESPONSE_CODES = {
|
||||||
"felica_lookup": 0x03,
|
"felica_lookup": 0x03,
|
||||||
"lookup": 0x06,
|
"lookup": 0x06,
|
||||||
"log": 0x0a,
|
"log": 0x0A,
|
||||||
"campaign": 0x0c,
|
"campaign": 0x0C,
|
||||||
"touch": 0x0e,
|
"touch": 0x0E,
|
||||||
"lookup2": 0x10,
|
"lookup2": 0x10,
|
||||||
"felica_lookup2": 0x12,
|
"felica_lookup2": 0x12,
|
||||||
"log2": 0x14,
|
"log2": 0x14,
|
||||||
"hello": 0x65
|
"hello": 0x65,
|
||||||
}
|
}
|
||||||
|
|
||||||
request_list: Dict[int, Any] = {}
|
request_list: Dict[int, Any] = {}
|
||||||
@@ -35,9 +36,9 @@ class AimedbProtocol(Protocol):
|
|||||||
self.request_list[0x04] = self.handle_lookup
|
self.request_list[0x04] = self.handle_lookup
|
||||||
self.request_list[0x05] = self.handle_register
|
self.request_list[0x05] = self.handle_register
|
||||||
self.request_list[0x09] = self.handle_log
|
self.request_list[0x09] = self.handle_log
|
||||||
self.request_list[0x0b] = self.handle_campaign
|
self.request_list[0x0B] = self.handle_campaign
|
||||||
self.request_list[0x0d] = self.handle_touch
|
self.request_list[0x0D] = self.handle_touch
|
||||||
self.request_list[0x0f] = self.handle_lookup2
|
self.request_list[0x0F] = self.handle_lookup2
|
||||||
self.request_list[0x11] = self.handle_felica_lookup2
|
self.request_list[0x11] = self.handle_felica_lookup2
|
||||||
self.request_list[0x13] = self.handle_log2
|
self.request_list[0x13] = self.handle_log2
|
||||||
self.request_list[0x64] = self.handle_hello
|
self.request_list[0x64] = self.handle_hello
|
||||||
@@ -53,7 +54,9 @@ class AimedbProtocol(Protocol):
|
|||||||
self.logger.debug(f"{self.transport.getPeer().host} Connected")
|
self.logger.debug(f"{self.transport.getPeer().host} Connected")
|
||||||
|
|
||||||
def connectionLost(self, reason) -> None:
|
def connectionLost(self, reason) -> None:
|
||||||
self.logger.debug(f"{self.transport.getPeer().host} Disconnected - {reason.value}")
|
self.logger.debug(
|
||||||
|
f"{self.transport.getPeer().host} Disconnected - {reason.value}"
|
||||||
|
)
|
||||||
|
|
||||||
def dataReceived(self, data: bytes) -> None:
|
def dataReceived(self, data: bytes) -> None:
|
||||||
cipher = AES.new(self.config.aimedb.key.encode(), AES.MODE_ECB)
|
cipher = AES.new(self.config.aimedb.key.encode(), AES.MODE_ECB)
|
||||||
@@ -66,7 +69,7 @@ class AimedbProtocol(Protocol):
|
|||||||
|
|
||||||
self.logger.debug(f"{self.transport.getPeer().host} wrote {decrypted.hex()}")
|
self.logger.debug(f"{self.transport.getPeer().host} wrote {decrypted.hex()}")
|
||||||
|
|
||||||
if not decrypted[1] == 0xa1 and not decrypted[0] == 0x3e:
|
if not decrypted[1] == 0xA1 and not decrypted[0] == 0x3E:
|
||||||
self.logger.error(f"Bad magic")
|
self.logger.error(f"Bad magic")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -93,27 +96,43 @@ class AimedbProtocol(Protocol):
|
|||||||
|
|
||||||
def handle_campaign(self, data: bytes) -> bytes:
|
def handle_campaign(self, data: bytes) -> bytes:
|
||||||
self.logger.info(f"campaign from {self.transport.getPeer().host}")
|
self.logger.info(f"campaign from {self.transport.getPeer().host}")
|
||||||
ret = struct.pack("<5H", 0xa13e, 0x3087, self.AIMEDB_RESPONSE_CODES["campaign"], 0x0200, 0x0001)
|
ret = struct.pack(
|
||||||
|
"<5H",
|
||||||
|
0xA13E,
|
||||||
|
0x3087,
|
||||||
|
self.AIMEDB_RESPONSE_CODES["campaign"],
|
||||||
|
0x0200,
|
||||||
|
0x0001,
|
||||||
|
)
|
||||||
return self.append_padding(ret)
|
return self.append_padding(ret)
|
||||||
|
|
||||||
def handle_hello(self, data: bytes) -> bytes:
|
def handle_hello(self, data: bytes) -> bytes:
|
||||||
self.logger.info(f"hello from {self.transport.getPeer().host}")
|
self.logger.info(f"hello from {self.transport.getPeer().host}")
|
||||||
ret = struct.pack("<5H", 0xa13e, 0x3087, self.AIMEDB_RESPONSE_CODES["hello"], 0x0020, 0x0001)
|
ret = struct.pack(
|
||||||
|
"<5H", 0xA13E, 0x3087, self.AIMEDB_RESPONSE_CODES["hello"], 0x0020, 0x0001
|
||||||
|
)
|
||||||
return self.append_padding(ret)
|
return self.append_padding(ret)
|
||||||
|
|
||||||
def handle_lookup(self, data: bytes) -> bytes:
|
def handle_lookup(self, data: bytes) -> bytes:
|
||||||
luid = data[0x20: 0x2a].hex()
|
luid = data[0x20:0x2A].hex()
|
||||||
user_id = self.data.card.get_user_id_from_card(access_code=luid)
|
user_id = self.data.card.get_user_id_from_card(access_code=luid)
|
||||||
|
|
||||||
if user_id is None: user_id = -1
|
if user_id is None:
|
||||||
|
user_id = -1
|
||||||
|
|
||||||
self.logger.info(f"lookup from {self.transport.getPeer().host}: luid {luid} -> user_id {user_id}")
|
self.logger.info(
|
||||||
|
f"lookup from {self.transport.getPeer().host}: luid {luid} -> user_id {user_id}"
|
||||||
|
)
|
||||||
|
|
||||||
ret = struct.pack("<5H", 0xa13e, 0x3087, self.AIMEDB_RESPONSE_CODES["lookup"], 0x0130, 0x0001)
|
ret = struct.pack(
|
||||||
|
"<5H", 0xA13E, 0x3087, self.AIMEDB_RESPONSE_CODES["lookup"], 0x0130, 0x0001
|
||||||
|
)
|
||||||
ret += bytes(0x20 - len(ret))
|
ret += bytes(0x20 - len(ret))
|
||||||
|
|
||||||
if user_id is None: ret += struct.pack("<iH", -1, 0)
|
if user_id is None:
|
||||||
else: ret += struct.pack("<l", user_id)
|
ret += struct.pack("<iH", -1, 0)
|
||||||
|
else:
|
||||||
|
ret += struct.pack("<l", user_id)
|
||||||
return self.append_padding(ret)
|
return self.append_padding(ret)
|
||||||
|
|
||||||
def handle_lookup2(self, data: bytes) -> bytes:
|
def handle_lookup2(self, data: bytes) -> bytes:
|
||||||
@@ -125,28 +144,47 @@ class AimedbProtocol(Protocol):
|
|||||||
return bytes(ret)
|
return bytes(ret)
|
||||||
|
|
||||||
def handle_felica_lookup(self, data: bytes) -> bytes:
|
def handle_felica_lookup(self, data: bytes) -> bytes:
|
||||||
idm = data[0x20: 0x28].hex()
|
idm = data[0x20:0x28].hex()
|
||||||
pmm = data[0x28: 0x30].hex()
|
pmm = data[0x28:0x30].hex()
|
||||||
access_code = self.data.card.to_access_code(idm)
|
access_code = self.data.card.to_access_code(idm)
|
||||||
self.logger.info(f"felica_lookup from {self.transport.getPeer().host}: idm {idm} pmm {pmm} -> access_code {access_code}")
|
self.logger.info(
|
||||||
|
f"felica_lookup from {self.transport.getPeer().host}: idm {idm} pmm {pmm} -> access_code {access_code}"
|
||||||
|
)
|
||||||
|
|
||||||
ret = struct.pack("<5H", 0xa13e, 0x3087, self.AIMEDB_RESPONSE_CODES["felica_lookup"], 0x0030, 0x0001)
|
ret = struct.pack(
|
||||||
|
"<5H",
|
||||||
|
0xA13E,
|
||||||
|
0x3087,
|
||||||
|
self.AIMEDB_RESPONSE_CODES["felica_lookup"],
|
||||||
|
0x0030,
|
||||||
|
0x0001,
|
||||||
|
)
|
||||||
ret += bytes(26)
|
ret += bytes(26)
|
||||||
ret += bytes.fromhex(access_code)
|
ret += bytes.fromhex(access_code)
|
||||||
|
|
||||||
return self.append_padding(ret)
|
return self.append_padding(ret)
|
||||||
|
|
||||||
def handle_felica_lookup2(self, data: bytes) -> bytes:
|
def handle_felica_lookup2(self, data: bytes) -> bytes:
|
||||||
idm = data[0x30: 0x38].hex()
|
idm = data[0x30:0x38].hex()
|
||||||
pmm = data[0x38: 0x40].hex()
|
pmm = data[0x38:0x40].hex()
|
||||||
access_code = self.data.card.to_access_code(idm)
|
access_code = self.data.card.to_access_code(idm)
|
||||||
user_id = self.data.card.get_user_id_from_card(access_code=access_code)
|
user_id = self.data.card.get_user_id_from_card(access_code=access_code)
|
||||||
|
|
||||||
if user_id is None: user_id = -1
|
if user_id is None:
|
||||||
|
user_id = -1
|
||||||
|
|
||||||
self.logger.info(f"felica_lookup2 from {self.transport.getPeer().host}: idm {idm} ipm {pmm} -> access_code {access_code} user_id {user_id}")
|
self.logger.info(
|
||||||
|
f"felica_lookup2 from {self.transport.getPeer().host}: idm {idm} ipm {pmm} -> access_code {access_code} user_id {user_id}"
|
||||||
|
)
|
||||||
|
|
||||||
ret = struct.pack("<5H", 0xa13e, 0x3087, self.AIMEDB_RESPONSE_CODES["felica_lookup2"], 0x0140, 0x0001)
|
ret = struct.pack(
|
||||||
|
"<5H",
|
||||||
|
0xA13E,
|
||||||
|
0x3087,
|
||||||
|
self.AIMEDB_RESPONSE_CODES["felica_lookup2"],
|
||||||
|
0x0140,
|
||||||
|
0x0001,
|
||||||
|
)
|
||||||
ret += bytes(22)
|
ret += bytes(22)
|
||||||
ret += struct.pack("<lq", user_id, -1) # first -1 is ext_id, 3rd is access code
|
ret += struct.pack("<lq", user_id, -1) # first -1 is ext_id, 3rd is access code
|
||||||
ret += bytes.fromhex(access_code)
|
ret += bytes.fromhex(access_code)
|
||||||
@@ -156,14 +194,16 @@ class AimedbProtocol(Protocol):
|
|||||||
|
|
||||||
def handle_touch(self, data: bytes) -> bytes:
|
def handle_touch(self, data: bytes) -> bytes:
|
||||||
self.logger.info(f"touch from {self.transport.getPeer().host}")
|
self.logger.info(f"touch from {self.transport.getPeer().host}")
|
||||||
ret = struct.pack("<5H", 0xa13e, 0x3087, self.AIMEDB_RESPONSE_CODES["touch"], 0x0050, 0x0001)
|
ret = struct.pack(
|
||||||
|
"<5H", 0xA13E, 0x3087, self.AIMEDB_RESPONSE_CODES["touch"], 0x0050, 0x0001
|
||||||
|
)
|
||||||
ret += bytes(5)
|
ret += bytes(5)
|
||||||
ret += struct.pack("<3H", 0x6f, 0, 1)
|
ret += struct.pack("<3H", 0x6F, 0, 1)
|
||||||
|
|
||||||
return self.append_padding(ret)
|
return self.append_padding(ret)
|
||||||
|
|
||||||
def handle_register(self, data: bytes) -> bytes:
|
def handle_register(self, data: bytes) -> bytes:
|
||||||
luid = data[0x20: 0x2a].hex()
|
luid = data[0x20:0x2A].hex()
|
||||||
if self.config.server.allow_user_registration:
|
if self.config.server.allow_user_registration:
|
||||||
user_id = self.data.user.create_user()
|
user_id = self.data.user.create_user()
|
||||||
|
|
||||||
@@ -178,13 +218,24 @@ class AimedbProtocol(Protocol):
|
|||||||
user_id = -1
|
user_id = -1
|
||||||
self.logger.error("Failed to register card!")
|
self.logger.error("Failed to register card!")
|
||||||
|
|
||||||
self.logger.info(f"register from {self.transport.getPeer().host}: luid {luid} -> user_id {user_id}")
|
self.logger.info(
|
||||||
|
f"register from {self.transport.getPeer().host}: luid {luid} -> user_id {user_id}"
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.logger.info(f"register from {self.transport.getPeer().host} blocked!: luid {luid}")
|
self.logger.info(
|
||||||
|
f"register from {self.transport.getPeer().host} blocked!: luid {luid}"
|
||||||
|
)
|
||||||
user_id = -1
|
user_id = -1
|
||||||
|
|
||||||
ret = struct.pack("<5H", 0xa13e, 0x3087, self.AIMEDB_RESPONSE_CODES["lookup"], 0x0030, 0x0001 if user_id > -1 else 0)
|
ret = struct.pack(
|
||||||
|
"<5H",
|
||||||
|
0xA13E,
|
||||||
|
0x3087,
|
||||||
|
self.AIMEDB_RESPONSE_CODES["lookup"],
|
||||||
|
0x0030,
|
||||||
|
0x0001 if user_id > -1 else 0,
|
||||||
|
)
|
||||||
ret += bytes(0x20 - len(ret))
|
ret += bytes(0x20 - len(ret))
|
||||||
ret += struct.pack("<l", user_id)
|
ret += struct.pack("<l", user_id)
|
||||||
|
|
||||||
@@ -193,26 +244,36 @@ class AimedbProtocol(Protocol):
|
|||||||
def handle_log(self, data: bytes) -> bytes:
|
def handle_log(self, data: bytes) -> bytes:
|
||||||
# TODO: Save aimedb logs
|
# TODO: Save aimedb logs
|
||||||
self.logger.info(f"log from {self.transport.getPeer().host}")
|
self.logger.info(f"log from {self.transport.getPeer().host}")
|
||||||
ret = struct.pack("<5H", 0xa13e, 0x3087, self.AIMEDB_RESPONSE_CODES["log"], 0x0020, 0x0001)
|
ret = struct.pack(
|
||||||
|
"<5H", 0xA13E, 0x3087, self.AIMEDB_RESPONSE_CODES["log"], 0x0020, 0x0001
|
||||||
|
)
|
||||||
return self.append_padding(ret)
|
return self.append_padding(ret)
|
||||||
|
|
||||||
def handle_log2(self, data: bytes) -> bytes:
|
def handle_log2(self, data: bytes) -> bytes:
|
||||||
self.logger.info(f"log2 from {self.transport.getPeer().host}")
|
self.logger.info(f"log2 from {self.transport.getPeer().host}")
|
||||||
ret = struct.pack("<5H", 0xa13e, 0x3087, self.AIMEDB_RESPONSE_CODES["log2"], 0x0040, 0x0001)
|
ret = struct.pack(
|
||||||
|
"<5H", 0xA13E, 0x3087, self.AIMEDB_RESPONSE_CODES["log2"], 0x0040, 0x0001
|
||||||
|
)
|
||||||
ret += bytes(22)
|
ret += bytes(22)
|
||||||
ret += struct.pack("H", 1)
|
ret += struct.pack("H", 1)
|
||||||
|
|
||||||
return self.append_padding(ret)
|
return self.append_padding(ret)
|
||||||
|
|
||||||
|
|
||||||
class AimedbFactory(Factory):
|
class AimedbFactory(Factory):
|
||||||
protocol = AimedbProtocol
|
protocol = AimedbProtocol
|
||||||
|
|
||||||
def __init__(self, cfg: CoreConfig) -> None:
|
def __init__(self, cfg: CoreConfig) -> None:
|
||||||
self.config = cfg
|
self.config = cfg
|
||||||
log_fmt_str = "[%(asctime)s] Aimedb | %(levelname)s | %(message)s"
|
log_fmt_str = "[%(asctime)s] Aimedb | %(levelname)s | %(message)s"
|
||||||
log_fmt = logging.Formatter(log_fmt_str)
|
log_fmt = logging.Formatter(log_fmt_str)
|
||||||
self.logger = logging.getLogger("aimedb")
|
self.logger = logging.getLogger("aimedb")
|
||||||
|
|
||||||
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.config.server.log_dir, "aimedb"), when="d", backupCount=10)
|
fileHandler = TimedRotatingFileHandler(
|
||||||
|
"{0}/{1}.log".format(self.config.server.log_dir, "aimedb"),
|
||||||
|
when="d",
|
||||||
|
backupCount=10,
|
||||||
|
)
|
||||||
fileHandler.setFormatter(log_fmt)
|
fileHandler.setFormatter(log_fmt)
|
||||||
|
|
||||||
consoleHandler = logging.StreamHandler()
|
consoleHandler = logging.StreamHandler()
|
||||||
@@ -222,7 +283,9 @@ class AimedbFactory(Factory):
|
|||||||
self.logger.addHandler(consoleHandler)
|
self.logger.addHandler(consoleHandler)
|
||||||
|
|
||||||
self.logger.setLevel(self.config.aimedb.loglevel)
|
self.logger.setLevel(self.config.aimedb.loglevel)
|
||||||
coloredlogs.install(level=cfg.aimedb.loglevel, logger=self.logger, fmt=log_fmt_str)
|
coloredlogs.install(
|
||||||
|
level=cfg.aimedb.loglevel, logger=self.logger, fmt=log_fmt_str
|
||||||
|
)
|
||||||
|
|
||||||
if self.config.aimedb.key == "":
|
if self.config.aimedb.key == "":
|
||||||
self.logger.error("Please set 'key' field in your config file.")
|
self.logger.error("Please set 'key' field in your config file.")
|
||||||
|
|||||||
+108
-54
@@ -16,6 +16,7 @@ from core.data import Data
|
|||||||
from core.utils import Utils
|
from core.utils import Utils
|
||||||
from core.const import *
|
from core.const import *
|
||||||
|
|
||||||
|
|
||||||
class AllnetServlet:
|
class AllnetServlet:
|
||||||
def __init__(self, core_cfg: CoreConfig, cfg_folder: str):
|
def __init__(self, core_cfg: CoreConfig, cfg_folder: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -29,7 +30,11 @@ class AllnetServlet:
|
|||||||
log_fmt_str = "[%(asctime)s] Allnet | %(levelname)s | %(message)s"
|
log_fmt_str = "[%(asctime)s] Allnet | %(levelname)s | %(message)s"
|
||||||
log_fmt = logging.Formatter(log_fmt_str)
|
log_fmt = logging.Formatter(log_fmt_str)
|
||||||
|
|
||||||
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.config.server.log_dir, "allnet"), when="d", backupCount=10)
|
fileHandler = TimedRotatingFileHandler(
|
||||||
|
"{0}/{1}.log".format(self.config.server.log_dir, "allnet"),
|
||||||
|
when="d",
|
||||||
|
backupCount=10,
|
||||||
|
)
|
||||||
fileHandler.setFormatter(log_fmt)
|
fileHandler.setFormatter(log_fmt)
|
||||||
|
|
||||||
consoleHandler = logging.StreamHandler()
|
consoleHandler = logging.StreamHandler()
|
||||||
@@ -39,7 +44,9 @@ class AllnetServlet:
|
|||||||
self.logger.addHandler(consoleHandler)
|
self.logger.addHandler(consoleHandler)
|
||||||
|
|
||||||
self.logger.setLevel(core_cfg.allnet.loglevel)
|
self.logger.setLevel(core_cfg.allnet.loglevel)
|
||||||
coloredlogs.install(level=core_cfg.allnet.loglevel, logger=self.logger, fmt=log_fmt_str)
|
coloredlogs.install(
|
||||||
|
level=core_cfg.allnet.loglevel, logger=self.logger, fmt=log_fmt_str
|
||||||
|
)
|
||||||
self.logger.initialized = True
|
self.logger.initialized = True
|
||||||
|
|
||||||
plugins = Utils.get_all_titles()
|
plugins = Utils.get_all_titles()
|
||||||
@@ -50,12 +57,16 @@ class AllnetServlet:
|
|||||||
for _, mod in plugins.items():
|
for _, mod in plugins.items():
|
||||||
if hasattr(mod.index, "get_allnet_info"):
|
if hasattr(mod.index, "get_allnet_info"):
|
||||||
for code in mod.game_codes:
|
for code in mod.game_codes:
|
||||||
enabled, uri, host = mod.index.get_allnet_info(code, self.config, self.config_folder)
|
enabled, uri, host = mod.index.get_allnet_info(
|
||||||
|
code, self.config, self.config_folder
|
||||||
|
)
|
||||||
|
|
||||||
if enabled:
|
if enabled:
|
||||||
self.uri_registry[code] = (uri, host)
|
self.uri_registry[code] = (uri, host)
|
||||||
|
|
||||||
self.logger.info(f"Allnet serving {len(self.uri_registry)} games on port {core_cfg.allnet.port}")
|
self.logger.info(
|
||||||
|
f"Allnet serving {len(self.uri_registry)} games on port {core_cfg.allnet.port}"
|
||||||
|
)
|
||||||
|
|
||||||
def handle_poweron(self, request: Request, _: Dict):
|
def handle_poweron(self, request: Request, _: Dict):
|
||||||
request_ip = request.getClientAddress().host
|
request_ip = request.getClientAddress().host
|
||||||
@@ -67,15 +78,17 @@ class AllnetServlet:
|
|||||||
req = AllnetPowerOnRequest(req_dict[0])
|
req = AllnetPowerOnRequest(req_dict[0])
|
||||||
# Validate the request. Currently we only validate the fields we plan on using
|
# Validate the request. Currently we only validate the fields we plan on using
|
||||||
|
|
||||||
if not req.game_id or not req.ver or not req.token or not req.serial or not req.ip:
|
if not req.game_id or not req.ver or not req.serial or not req.ip:
|
||||||
raise AllnetRequestException(f"Bad auth request params from {request_ip} - {vars(req)}")
|
raise AllnetRequestException(
|
||||||
|
f"Bad auth request params from {request_ip} - {vars(req)}"
|
||||||
|
)
|
||||||
|
|
||||||
except AllnetRequestException as e:
|
except AllnetRequestException as e:
|
||||||
if e.message != "":
|
if e.message != "":
|
||||||
self.logger.error(e)
|
self.logger.error(e)
|
||||||
return b""
|
return b""
|
||||||
|
|
||||||
if req.format_ver == 3:
|
if req.format_ver == "3":
|
||||||
resp = AllnetPowerOnResponse3(req.token)
|
resp = AllnetPowerOnResponse3(req.token)
|
||||||
else:
|
else:
|
||||||
resp = AllnetPowerOnResponse2()
|
resp = AllnetPowerOnResponse2()
|
||||||
@@ -83,7 +96,9 @@ class AllnetServlet:
|
|||||||
self.logger.debug(f"Allnet request: {vars(req)}")
|
self.logger.debug(f"Allnet request: {vars(req)}")
|
||||||
if req.game_id not in self.uri_registry:
|
if req.game_id not in self.uri_registry:
|
||||||
msg = f"Unrecognised game {req.game_id} attempted allnet auth from {request_ip}."
|
msg = f"Unrecognised game {req.game_id} attempted allnet auth from {request_ip}."
|
||||||
self.data.base.log_event("allnet", "ALLNET_AUTH_UNKNOWN_GAME", logging.WARN, msg)
|
self.data.base.log_event(
|
||||||
|
"allnet", "ALLNET_AUTH_UNKNOWN_GAME", logging.WARN, msg
|
||||||
|
)
|
||||||
self.logger.warn(msg)
|
self.logger.warn(msg)
|
||||||
|
|
||||||
resp.stat = 0
|
resp.stat = 0
|
||||||
@@ -94,7 +109,9 @@ class AllnetServlet:
|
|||||||
machine = self.data.arcade.get_machine(req.serial)
|
machine = self.data.arcade.get_machine(req.serial)
|
||||||
if machine is None and not self.config.server.allow_unregistered_serials:
|
if machine is None and not self.config.server.allow_unregistered_serials:
|
||||||
msg = f"Unrecognised serial {req.serial} attempted allnet auth from {request_ip}."
|
msg = f"Unrecognised serial {req.serial} attempted allnet auth from {request_ip}."
|
||||||
self.data.base.log_event("allnet", "ALLNET_AUTH_UNKNOWN_SERIAL", logging.WARN, msg)
|
self.data.base.log_event(
|
||||||
|
"allnet", "ALLNET_AUTH_UNKNOWN_SERIAL", logging.WARN, msg
|
||||||
|
)
|
||||||
self.logger.warn(msg)
|
self.logger.warn(msg)
|
||||||
|
|
||||||
resp.stat = 0
|
resp.stat = 0
|
||||||
@@ -102,7 +119,9 @@ class AllnetServlet:
|
|||||||
|
|
||||||
if machine is not None:
|
if machine is not None:
|
||||||
arcade = self.data.arcade.get_arcade(machine["arcade"])
|
arcade = self.data.arcade.get_arcade(machine["arcade"])
|
||||||
country = arcade["country"] if machine["country"] is None else machine["country"]
|
country = (
|
||||||
|
arcade["country"] if machine["country"] is None else machine["country"]
|
||||||
|
)
|
||||||
if country is None:
|
if country is None:
|
||||||
country = AllnetCountryCode.JAPAN.value
|
country = AllnetCountryCode.JAPAN.value
|
||||||
|
|
||||||
@@ -111,11 +130,25 @@ class AllnetServlet:
|
|||||||
resp.allnet_id = machine["id"]
|
resp.allnet_id = machine["id"]
|
||||||
resp.name = arcade["name"] if arcade["name"] is not None else ""
|
resp.name = arcade["name"] if arcade["name"] is not None else ""
|
||||||
resp.nickname = arcade["nickname"] if arcade["nickname"] is not None else ""
|
resp.nickname = arcade["nickname"] if arcade["nickname"] is not None else ""
|
||||||
resp.region0 = arcade["region_id"] if arcade["region_id"] is not None else AllnetJapanRegionId.AICHI.value
|
resp.region0 = (
|
||||||
resp.region_name0 = arcade["country"] if arcade["country"] is not None else AllnetCountryCode.JAPAN.value
|
arcade["region_id"]
|
||||||
resp.region_name1 = arcade["state"] if arcade["state"] is not None else AllnetJapanRegionId.AICHI.name
|
if arcade["region_id"] is not None
|
||||||
|
else AllnetJapanRegionId.AICHI.value
|
||||||
|
)
|
||||||
|
resp.region_name0 = (
|
||||||
|
arcade["country"]
|
||||||
|
if arcade["country"] is not None
|
||||||
|
else AllnetCountryCode.JAPAN.value
|
||||||
|
)
|
||||||
|
resp.region_name1 = (
|
||||||
|
arcade["state"]
|
||||||
|
if arcade["state"] is not None
|
||||||
|
else AllnetJapanRegionId.AICHI.name
|
||||||
|
)
|
||||||
resp.region_name2 = arcade["city"] if arcade["city"] is not None else ""
|
resp.region_name2 = arcade["city"] if arcade["city"] is not None else ""
|
||||||
resp.client_timezone = arcade["timezone"] if arcade["timezone"] is not None else "+0900"
|
resp.client_timezone = (
|
||||||
|
arcade["timezone"] if arcade["timezone"] is not None else "+0900"
|
||||||
|
)
|
||||||
|
|
||||||
int_ver = req.ver.replace(".", "")
|
int_ver = req.ver.replace(".", "")
|
||||||
resp.uri = resp.uri.replace("$v", int_ver)
|
resp.uri = resp.uri.replace("$v", int_ver)
|
||||||
@@ -139,7 +172,9 @@ class AllnetServlet:
|
|||||||
# Validate the request. Currently we only validate the fields we plan on using
|
# Validate the request. Currently we only validate the fields we plan on using
|
||||||
|
|
||||||
if not req.game_id or not req.ver or not req.serial:
|
if not req.game_id or not req.ver or not req.serial:
|
||||||
raise AllnetRequestException(f"Bad download request params from {request_ip} - {vars(req)}")
|
raise AllnetRequestException(
|
||||||
|
f"Bad download request params from {request_ip} - {vars(req)}"
|
||||||
|
)
|
||||||
|
|
||||||
except AllnetRequestException as e:
|
except AllnetRequestException as e:
|
||||||
if e.message != "":
|
if e.message != "":
|
||||||
@@ -162,7 +197,7 @@ class AllnetServlet:
|
|||||||
|
|
||||||
self.logger.debug(f"request {req_dict}")
|
self.logger.debug(f"request {req_dict}")
|
||||||
|
|
||||||
rsa = RSA.import_key(open(self.config.billing.signing_key, 'rb').read())
|
rsa = RSA.import_key(open(self.config.billing.signing_key, "rb").read())
|
||||||
signer = PKCS1_v1_5.new(rsa)
|
signer = PKCS1_v1_5.new(rsa)
|
||||||
digest = SHA.new()
|
digest = SHA.new()
|
||||||
|
|
||||||
@@ -178,17 +213,21 @@ class AllnetServlet:
|
|||||||
machine = self.data.arcade.get_machine(kc_serial)
|
machine = self.data.arcade.get_machine(kc_serial)
|
||||||
if machine is None and not self.config.server.allow_unregistered_serials:
|
if machine is None and not self.config.server.allow_unregistered_serials:
|
||||||
msg = f"Unrecognised serial {kc_serial} attempted billing checkin from {request_ip} for game {kc_game}."
|
msg = f"Unrecognised serial {kc_serial} attempted billing checkin from {request_ip} for game {kc_game}."
|
||||||
self.data.base.log_event("allnet", "BILLING_CHECKIN_NG_SERIAL", logging.WARN, msg)
|
self.data.base.log_event(
|
||||||
|
"allnet", "BILLING_CHECKIN_NG_SERIAL", logging.WARN, msg
|
||||||
|
)
|
||||||
self.logger.warn(msg)
|
self.logger.warn(msg)
|
||||||
|
|
||||||
resp = BillingResponse("", "", "", "")
|
resp = BillingResponse("", "", "", "")
|
||||||
resp.result = "1"
|
resp.result = "1"
|
||||||
return self.dict_to_http_form_string([vars(resp)])
|
return self.dict_to_http_form_string([vars(resp)])
|
||||||
|
|
||||||
msg = f"Billing checkin from {request.getClientIP()}: game {kc_game} keychip {kc_serial} playcount " \
|
msg = (
|
||||||
|
f"Billing checkin from {request.getClientIP()}: game {kc_game} keychip {kc_serial} playcount "
|
||||||
f"{kc_playcount} billing_type {kc_billigtype} nearfull {kc_nearfull} playlimit {kc_playlimit}"
|
f"{kc_playcount} billing_type {kc_billigtype} nearfull {kc_nearfull} playlimit {kc_playlimit}"
|
||||||
|
)
|
||||||
self.logger.info(msg)
|
self.logger.info(msg)
|
||||||
self.data.base.log_event('billing', 'BILLING_CHECKIN_OK', logging.INFO, msg)
|
self.data.base.log_event("billing", "BILLING_CHECKIN_OK", logging.INFO, msg)
|
||||||
|
|
||||||
while kc_playcount > kc_playlimit:
|
while kc_playcount > kc_playlimit:
|
||||||
kc_playlimit += 1024
|
kc_playlimit += 1024
|
||||||
@@ -197,11 +236,11 @@ class AllnetServlet:
|
|||||||
playlimit = kc_playlimit
|
playlimit = kc_playlimit
|
||||||
nearfull = kc_nearfull + (kc_billigtype * 0x00010000)
|
nearfull = kc_nearfull + (kc_billigtype * 0x00010000)
|
||||||
|
|
||||||
digest.update(playlimit.to_bytes(4, 'little') + kc_serial_bytes)
|
digest.update(playlimit.to_bytes(4, "little") + kc_serial_bytes)
|
||||||
playlimit_sig = signer.sign(digest).hex()
|
playlimit_sig = signer.sign(digest).hex()
|
||||||
|
|
||||||
digest = SHA.new()
|
digest = SHA.new()
|
||||||
digest.update(nearfull.to_bytes(4, 'little') + kc_serial_bytes)
|
digest.update(nearfull.to_bytes(4, "little") + kc_serial_bytes)
|
||||||
nearfull_sig = signer.sign(digest).hex()
|
nearfull_sig = signer.sign(digest).hex()
|
||||||
|
|
||||||
# TODO: playhistory
|
# TODO: playhistory
|
||||||
@@ -222,11 +261,11 @@ class AllnetServlet:
|
|||||||
def kvp_to_dict(self, kvp: List[str]) -> List[Dict[str, Any]]:
|
def kvp_to_dict(self, kvp: List[str]) -> List[Dict[str, Any]]:
|
||||||
ret: List[Dict[str, Any]] = []
|
ret: List[Dict[str, Any]] = []
|
||||||
for x in kvp:
|
for x in kvp:
|
||||||
items = x.split('&')
|
items = x.split("&")
|
||||||
tmp = {}
|
tmp = {}
|
||||||
|
|
||||||
for item in items:
|
for item in items:
|
||||||
kvp = item.split('=')
|
kvp = item.split("=")
|
||||||
if len(kvp) == 2:
|
if len(kvp) == 2:
|
||||||
tmp[kvp[0]] = kvp[1]
|
tmp[kvp[0]] = kvp[1]
|
||||||
|
|
||||||
@@ -241,7 +280,7 @@ class AllnetServlet:
|
|||||||
try:
|
try:
|
||||||
decomp = zlib.decompressobj(-zlib.MAX_WBITS)
|
decomp = zlib.decompressobj(-zlib.MAX_WBITS)
|
||||||
unzipped = decomp.decompress(data)
|
unzipped = decomp.decompress(data)
|
||||||
sections = unzipped.decode('ascii').split('\r\n')
|
sections = unzipped.decode("ascii").split("\r\n")
|
||||||
|
|
||||||
return self.kvp_to_dict(sections)
|
return self.kvp_to_dict(sections)
|
||||||
|
|
||||||
@@ -256,7 +295,7 @@ class AllnetServlet:
|
|||||||
try:
|
try:
|
||||||
zipped = base64.b64decode(data)
|
zipped = base64.b64decode(data)
|
||||||
unzipped = zlib.decompress(zipped)
|
unzipped = zlib.decompress(zipped)
|
||||||
sections = unzipped.decode('utf-8').split('\r\n')
|
sections = unzipped.decode("utf-8").split("\r\n")
|
||||||
|
|
||||||
return self.kvp_to_dict(sections)
|
return self.kvp_to_dict(sections)
|
||||||
|
|
||||||
@@ -264,14 +303,19 @@ class AllnetServlet:
|
|||||||
self.logger.error(f"allnet_req_to_dict: {e} while parsing {data}")
|
self.logger.error(f"allnet_req_to_dict: {e} while parsing {data}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def dict_to_http_form_string(self, data:List[Dict[str, Any]], crlf: bool = False, trailing_newline: bool = True) -> Optional[str]:
|
def dict_to_http_form_string(
|
||||||
|
self,
|
||||||
|
data: List[Dict[str, Any]],
|
||||||
|
crlf: bool = False,
|
||||||
|
trailing_newline: bool = True,
|
||||||
|
) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
Takes a python dictionary and parses it into an allnet response string
|
Takes a python dictionary and parses it into an allnet response string
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
urlencode = ""
|
urlencode = ""
|
||||||
for item in data:
|
for item in data:
|
||||||
for k,v in item.items():
|
for k, v in item.items():
|
||||||
urlencode += f"{k}={v}&"
|
urlencode += f"{k}={v}&"
|
||||||
|
|
||||||
if crlf:
|
if crlf:
|
||||||
@@ -291,26 +335,24 @@ class AllnetServlet:
|
|||||||
self.logger.error(f"dict_to_http_form_string: {e} while parsing {data}")
|
self.logger.error(f"dict_to_http_form_string: {e} while parsing {data}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
class AllnetPowerOnRequest():
|
|
||||||
|
class AllnetPowerOnRequest:
|
||||||
def __init__(self, req: Dict) -> None:
|
def __init__(self, req: Dict) -> None:
|
||||||
if req is None:
|
if req is None:
|
||||||
raise AllnetRequestException("Request processing failed")
|
raise AllnetRequestException("Request processing failed")
|
||||||
self.game_id: str = req["game_id"] if "game_id" in req else ""
|
self.game_id: str = req.get("game_id", "")
|
||||||
self.ver: str = req["ver"] if "ver" in req else ""
|
self.ver: str = req.get("ver", "")
|
||||||
self.serial: str = req["serial"] if "serial" in req else ""
|
self.serial: str = req.get("serial", "")
|
||||||
self.ip: str = req["ip"] if "ip" in req else ""
|
self.ip: str = req.get("ip", "")
|
||||||
self.firm_ver: str = req["firm_ver"] if "firm_ver" in req else ""
|
self.firm_ver: str = req.get("firm_ver", "")
|
||||||
self.boot_ver: str = req["boot_ver"] if "boot_ver" in req else ""
|
self.boot_ver: str = req.get("boot_ver", "")
|
||||||
self.encode: str = req["encode"] if "encode" in req else ""
|
self.encode: str = req.get("encode", "")
|
||||||
|
self.hops = int(req.get("hops", "0"))
|
||||||
|
self.format_ver = req.get("format_ver", "2")
|
||||||
|
self.token = int(req.get("token", "0"))
|
||||||
|
|
||||||
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
|
|
||||||
self.token = int(req["token"]) if "token" in req else 0
|
|
||||||
except ValueError as e:
|
|
||||||
raise AllnetRequestException(f"Failed to parse int: {e}")
|
|
||||||
|
|
||||||
class AllnetPowerOnResponse3():
|
class AllnetPowerOnResponse3:
|
||||||
def __init__(self, token) -> None:
|
def __init__(self, token) -> None:
|
||||||
self.stat = 1
|
self.stat = 1
|
||||||
self.uri = ""
|
self.uri = ""
|
||||||
@@ -326,12 +368,15 @@ class AllnetPowerOnResponse3():
|
|||||||
self.country = "JPN"
|
self.country = "JPN"
|
||||||
self.allnet_id = "123"
|
self.allnet_id = "123"
|
||||||
self.client_timezone = "+0900"
|
self.client_timezone = "+0900"
|
||||||
self.utc_time = datetime.now(tz=pytz.timezone('UTC')).strftime("%Y-%m-%dT%H:%M:%SZ")
|
self.utc_time = datetime.now(tz=pytz.timezone("UTC")).strftime(
|
||||||
|
"%Y-%m-%dT%H:%M:%SZ"
|
||||||
|
)
|
||||||
self.setting = ""
|
self.setting = ""
|
||||||
self.res_ver = "3"
|
self.res_ver = "3"
|
||||||
self.token = str(token)
|
self.token = str(token)
|
||||||
|
|
||||||
class AllnetPowerOnResponse2():
|
|
||||||
|
class AllnetPowerOnResponse2:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.stat = 1
|
self.stat = 1
|
||||||
self.uri = ""
|
self.uri = ""
|
||||||
@@ -355,23 +400,31 @@ class AllnetPowerOnResponse2():
|
|||||||
self.timezone = "+0900"
|
self.timezone = "+0900"
|
||||||
self.res_class = "PowerOnResponseV2"
|
self.res_class = "PowerOnResponseV2"
|
||||||
|
|
||||||
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 AllnetDownloadOrderRequest:
|
||||||
|
def __init__(self, req: Dict) -> None:
|
||||||
|
self.game_id = req.get("game_id", "")
|
||||||
|
self.ver = req.get("ver", "")
|
||||||
|
self.serial = req.get("serial", "")
|
||||||
|
self.encode = req.get("encode", "")
|
||||||
|
|
||||||
|
|
||||||
|
class AllnetDownloadOrderResponse:
|
||||||
def __init__(self, stat: int = 1, serial: str = "", uri: str = "null") -> None:
|
def __init__(self, stat: int = 1, serial: str = "", uri: str = "null") -> None:
|
||||||
self.stat = stat
|
self.stat = stat
|
||||||
self.serial = serial
|
self.serial = serial
|
||||||
self.uri = uri
|
self.uri = uri
|
||||||
|
|
||||||
class BillingResponse():
|
|
||||||
def __init__(self, playlimit: str = "", playlimit_sig: str = "", nearfull: str = "", nearfull_sig: str = "",
|
|
||||||
playhistory: str = "000000/0:000000/0:000000/0") -> None:
|
|
||||||
|
|
||||||
|
class BillingResponse:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
playlimit: str = "",
|
||||||
|
playlimit_sig: str = "",
|
||||||
|
nearfull: str = "",
|
||||||
|
nearfull_sig: str = "",
|
||||||
|
playhistory: str = "000000/0:000000/0:000000/0",
|
||||||
|
) -> None:
|
||||||
self.result = "0"
|
self.result = "0"
|
||||||
self.waitime = "100"
|
self.waitime = "100"
|
||||||
self.linelimit = "1"
|
self.linelimit = "1"
|
||||||
@@ -387,6 +440,7 @@ class BillingResponse():
|
|||||||
# playhistory -> YYYYMM/C:...
|
# playhistory -> YYYYMM/C:...
|
||||||
# YYYY -> 4 digit year, MM -> 2 digit month, C -> Playcount during that period
|
# YYYY -> 4 digit year, MM -> 2 digit month, C -> Playcount during that period
|
||||||
|
|
||||||
|
|
||||||
class AllnetRequestException(Exception):
|
class AllnetRequestException(Exception):
|
||||||
def __init__(self, message="") -> None:
|
def __init__(self, message="") -> None:
|
||||||
self.message = message
|
self.message = message
|
||||||
|
|||||||
+145
-42
@@ -1,33 +1,47 @@
|
|||||||
import logging, os
|
import logging, os
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
class ServerConfig:
|
class ServerConfig:
|
||||||
def __init__(self, parent_config: "CoreConfig") -> None:
|
def __init__(self, parent_config: "CoreConfig") -> None:
|
||||||
self.__config = parent_config
|
self.__config = parent_config
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def listen_address(self) -> str:
|
def listen_address(self) -> str:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'server', 'listen_address', default='127.0.0.1')
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "server", "listen_address", default="127.0.0.1"
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def allow_user_registration(self) -> bool:
|
def allow_user_registration(self) -> bool:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'server', 'allow_user_registration', default=True)
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "server", "allow_user_registration", default=True
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def allow_unregistered_serials(self) -> bool:
|
def allow_unregistered_serials(self) -> bool:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'server', 'allow_unregistered_serials', default=True)
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "server", "allow_unregistered_serials", default=True
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'server', 'name', default="ARTEMiS")
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "server", "name", default="ARTEMiS"
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_develop(self) -> bool:
|
def is_develop(self) -> bool:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'server', 'is_develop', default=True)
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "server", "is_develop", default=True
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def log_dir(self) -> str:
|
def log_dir(self) -> str:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'server', 'log_dir', default='logs')
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "server", "log_dir", default="logs"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TitleConfig:
|
class TitleConfig:
|
||||||
def __init__(self, parent_config: "CoreConfig") -> None:
|
def __init__(self, parent_config: "CoreConfig") -> None:
|
||||||
@@ -35,15 +49,24 @@ class TitleConfig:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def loglevel(self) -> int:
|
def loglevel(self) -> int:
|
||||||
return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'core', 'title', 'loglevel', default="info"))
|
return CoreConfig.str_to_loglevel(
|
||||||
|
CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "title", "loglevel", default="info"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hostname(self) -> str:
|
def hostname(self) -> str:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'title', 'hostname', default="localhost")
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "title", "hostname", default="localhost"
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def port(self) -> int:
|
def port(self) -> int:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'title', 'port', default=8080)
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "title", "port", default=8080
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DatabaseConfig:
|
class DatabaseConfig:
|
||||||
def __init__(self, parent_config: "CoreConfig") -> None:
|
def __init__(self, parent_config: "CoreConfig") -> None:
|
||||||
@@ -51,43 +74,70 @@ class DatabaseConfig:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def host(self) -> str:
|
def host(self) -> str:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'database', 'host', default="localhost")
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "database", "host", default="localhost"
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def username(self) -> str:
|
def username(self) -> str:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'database', 'username', default='aime')
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "database", "username", default="aime"
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def password(self) -> str:
|
def password(self) -> str:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'database', 'password', default='aime')
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "database", "password", default="aime"
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'database', 'name', default='aime')
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "database", "name", default="aime"
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def port(self) -> int:
|
def port(self) -> int:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'database', 'port', default=3306)
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "database", "port", default=3306
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def protocol(self) -> str:
|
def protocol(self) -> str:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'database', 'type', default="mysql")
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "database", "type", default="mysql"
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sha2_password(self) -> bool:
|
def sha2_password(self) -> bool:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'database', 'sha2_password', default=False)
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "database", "sha2_password", default=False
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def loglevel(self) -> int:
|
def loglevel(self) -> int:
|
||||||
return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'core', 'database', 'loglevel', default="info"))
|
return CoreConfig.str_to_loglevel(
|
||||||
|
CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "database", "loglevel", default="info"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def user_table_autoincrement_start(self) -> int:
|
def user_table_autoincrement_start(self) -> int:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'database', 'user_table_autoincrement_start', default=10000)
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config,
|
||||||
|
"core",
|
||||||
|
"database",
|
||||||
|
"user_table_autoincrement_start",
|
||||||
|
default=10000,
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def memcached_host(self) -> str:
|
def memcached_host(self) -> str:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'database', 'memcached_host', default="localhost")
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "database", "memcached_host", default="localhost"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class FrontendConfig:
|
class FrontendConfig:
|
||||||
def __init__(self, parent_config: "CoreConfig") -> None:
|
def __init__(self, parent_config: "CoreConfig") -> None:
|
||||||
@@ -95,15 +145,24 @@ class FrontendConfig:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def enable(self) -> int:
|
def enable(self) -> int:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'frontend', 'enable', default=False)
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "frontend", "enable", default=False
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def port(self) -> int:
|
def port(self) -> int:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'frontend', 'port', default=8090)
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "frontend", "port", default=8090
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def loglevel(self) -> int:
|
def loglevel(self) -> int:
|
||||||
return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'core', 'frontend', 'loglevel', default="info"))
|
return CoreConfig.str_to_loglevel(
|
||||||
|
CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "frontend", "loglevel", default="info"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AllnetConfig:
|
class AllnetConfig:
|
||||||
def __init__(self, parent_config: "CoreConfig") -> None:
|
def __init__(self, parent_config: "CoreConfig") -> None:
|
||||||
@@ -111,15 +170,24 @@ class AllnetConfig:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def loglevel(self) -> int:
|
def loglevel(self) -> int:
|
||||||
return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'core', 'allnet', 'loglevel', default="info"))
|
return CoreConfig.str_to_loglevel(
|
||||||
|
CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "allnet", "loglevel", default="info"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def port(self) -> int:
|
def port(self) -> int:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'allnet', 'port', default=80)
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "allnet", "port", default=80
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def allow_online_updates(self) -> int:
|
def allow_online_updates(self) -> int:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'allnet', 'allow_online_updates', default=False)
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "allnet", "allow_online_updates", default=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class BillingConfig:
|
class BillingConfig:
|
||||||
def __init__(self, parent_config: "CoreConfig") -> None:
|
def __init__(self, parent_config: "CoreConfig") -> None:
|
||||||
@@ -127,19 +195,28 @@ class BillingConfig:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def port(self) -> int:
|
def port(self) -> int:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'billing', 'port', default=8443)
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "billing", "port", default=8443
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ssl_key(self) -> str:
|
def ssl_key(self) -> str:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'billing', 'ssl_key', default="cert/server.key")
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "billing", "ssl_key", default="cert/server.key"
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ssl_cert(self) -> str:
|
def ssl_cert(self) -> str:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'billing', 'ssl_cert', default="cert/server.pem")
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "billing", "ssl_cert", default="cert/server.pem"
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def signing_key(self) -> str:
|
def signing_key(self) -> str:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'billing', 'signing_key', default="cert/billing.key")
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "billing", "signing_key", default="cert/billing.key"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AimedbConfig:
|
class AimedbConfig:
|
||||||
def __init__(self, parent_config: "CoreConfig") -> None:
|
def __init__(self, parent_config: "CoreConfig") -> None:
|
||||||
@@ -147,15 +224,24 @@ class AimedbConfig:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def loglevel(self) -> int:
|
def loglevel(self) -> int:
|
||||||
return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'core', 'aimedb', 'loglevel', default="info"))
|
return CoreConfig.str_to_loglevel(
|
||||||
|
CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "aimedb", "loglevel", default="info"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def port(self) -> int:
|
def port(self) -> int:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'aimedb', 'port', default=22345)
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "aimedb", "port", default=22345
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def key(self) -> str:
|
def key(self) -> str:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'aimedb', 'key', default="")
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "aimedb", "key", default=""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MuchaConfig:
|
class MuchaConfig:
|
||||||
def __init__(self, parent_config: "CoreConfig") -> None:
|
def __init__(self, parent_config: "CoreConfig") -> None:
|
||||||
@@ -163,27 +249,42 @@ class MuchaConfig:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def enable(self) -> int:
|
def enable(self) -> int:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'mucha', 'enable', default=False)
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "mucha", "enable", default=False
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def loglevel(self) -> int:
|
def loglevel(self) -> int:
|
||||||
return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'core', 'mucha', 'loglevel', default="info"))
|
return CoreConfig.str_to_loglevel(
|
||||||
|
CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "mucha", "loglevel", default="info"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hostname(self) -> str:
|
def hostname(self) -> str:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'mucha', 'hostname', default="localhost")
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "mucha", "hostname", default="localhost"
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def port(self) -> int:
|
def port(self) -> int:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'mucha', 'port', default=8444)
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "mucha", "port", default=8444
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ssl_cert(self) -> str:
|
def ssl_cert(self) -> str:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'mucha', 'ssl_cert', default="cert/server.pem")
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "mucha", "ssl_cert", default="cert/server.pem"
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def signing_key(self) -> str:
|
def signing_key(self) -> str:
|
||||||
return CoreConfig.get_config_field(self.__config, 'core', 'mucha', 'signing_key', default="cert/billing.key")
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "mucha", "signing_key", default="cert/billing.key"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CoreConfig(dict):
|
class CoreConfig(dict):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
@@ -208,12 +309,14 @@ class CoreConfig(dict):
|
|||||||
return logging.INFO
|
return logging.INFO
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_config_field(cls, __config: dict, module, *path: str, default: Any = "") -> Any:
|
def get_config_field(
|
||||||
envKey = f'CFG_{module}_'
|
cls, __config: dict, module, *path: str, default: Any = ""
|
||||||
|
) -> Any:
|
||||||
|
envKey = f"CFG_{module}_"
|
||||||
for arg in path:
|
for arg in path:
|
||||||
envKey += arg + '_'
|
envKey += arg + "_"
|
||||||
|
|
||||||
if envKey.endswith('_'):
|
if envKey.endswith("_"):
|
||||||
envKey = envKey[:-1]
|
envKey = envKey[:-1]
|
||||||
|
|
||||||
if envKey in os.environ:
|
if envKey in os.environ:
|
||||||
|
|||||||
+8
-3
@@ -1,6 +1,7 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
class MainboardPlatformCodes():
|
|
||||||
|
class MainboardPlatformCodes:
|
||||||
RINGEDGE = "AALE"
|
RINGEDGE = "AALE"
|
||||||
RINGWIDE = "AAML"
|
RINGWIDE = "AAML"
|
||||||
NU = "AAVE"
|
NU = "AAVE"
|
||||||
@@ -8,7 +9,8 @@ class MainboardPlatformCodes():
|
|||||||
ALLS_UX = "ACAE"
|
ALLS_UX = "ACAE"
|
||||||
ALLS_HX = "ACAX"
|
ALLS_HX = "ACAX"
|
||||||
|
|
||||||
class MainboardRevisions():
|
|
||||||
|
class MainboardRevisions:
|
||||||
RINGEDGE = 1
|
RINGEDGE = 1
|
||||||
RINGEDGE2 = 2
|
RINGEDGE2 = 2
|
||||||
|
|
||||||
@@ -26,12 +28,14 @@ class MainboardRevisions():
|
|||||||
ALLS_UX2 = 2
|
ALLS_UX2 = 2
|
||||||
ALLS_HX2 = 12
|
ALLS_HX2 = 12
|
||||||
|
|
||||||
class KeychipPlatformsCodes():
|
|
||||||
|
class KeychipPlatformsCodes:
|
||||||
RING = "A72E"
|
RING = "A72E"
|
||||||
NU = ("A60E", "A60E", "A60E")
|
NU = ("A60E", "A60E", "A60E")
|
||||||
NUSX = ("A61X", "A69X")
|
NUSX = ("A61X", "A69X")
|
||||||
ALLS = "A63E"
|
ALLS = "A63E"
|
||||||
|
|
||||||
|
|
||||||
class AllnetCountryCode(Enum):
|
class AllnetCountryCode(Enum):
|
||||||
JAPAN = "JPN"
|
JAPAN = "JPN"
|
||||||
UNITED_STATES = "USA"
|
UNITED_STATES = "USA"
|
||||||
@@ -41,6 +45,7 @@ class AllnetCountryCode(Enum):
|
|||||||
TAIWAN = "TWN"
|
TAIWAN = "TWN"
|
||||||
CHINA = "CHN"
|
CHINA = "CHN"
|
||||||
|
|
||||||
|
|
||||||
class AllnetJapanRegionId(Enum):
|
class AllnetJapanRegionId(Enum):
|
||||||
NONE = 0
|
NONE = 0
|
||||||
AICHI = 1
|
AICHI = 1
|
||||||
|
|||||||
+6
-4
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
from typing import Any, Callable
|
from typing import Any, Callable
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
import hashlib
|
import hashlib
|
||||||
@@ -6,15 +5,17 @@ import pickle
|
|||||||
import logging
|
import logging
|
||||||
from core.config import CoreConfig
|
from core.config import CoreConfig
|
||||||
|
|
||||||
cfg:CoreConfig = None # type: ignore
|
cfg: CoreConfig = None # type: ignore
|
||||||
# Make memcache optional
|
# Make memcache optional
|
||||||
try:
|
try:
|
||||||
import pylibmc # type: ignore
|
import pylibmc # type: ignore
|
||||||
|
|
||||||
has_mc = True
|
has_mc = True
|
||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
has_mc = False
|
has_mc = False
|
||||||
|
|
||||||
def cached(lifetime: int=10, extra_key: Any=None) -> Callable:
|
|
||||||
|
def cached(lifetime: int = 10, extra_key: Any = None) -> Callable:
|
||||||
def _cached(func: Callable) -> Callable:
|
def _cached(func: Callable) -> Callable:
|
||||||
if has_mc:
|
if has_mc:
|
||||||
hostname = "127.0.0.1"
|
hostname = "127.0.0.1"
|
||||||
@@ -26,7 +27,6 @@ def cached(lifetime: int=10, extra_key: Any=None) -> Callable:
|
|||||||
@wraps(func)
|
@wraps(func)
|
||||||
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
||||||
if lifetime is not None:
|
if lifetime is not None:
|
||||||
|
|
||||||
# Hash function args
|
# Hash function args
|
||||||
items = kwargs.items()
|
items = kwargs.items()
|
||||||
hashable_args = (args[1:], sorted(list(items)))
|
hashable_args = (args[1:], sorted(list(items)))
|
||||||
@@ -55,7 +55,9 @@ def cached(lifetime: int=10, extra_key: Any=None) -> Callable:
|
|||||||
memcache.set(cache_key, result, lifetime)
|
memcache.set(cache_key, result, lifetime)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
|
|||||||
+62
-23
@@ -13,6 +13,7 @@ from core.config import CoreConfig
|
|||||||
from core.data.schema import *
|
from core.data.schema import *
|
||||||
from core.utils import Utils
|
from core.utils import Utils
|
||||||
|
|
||||||
|
|
||||||
class Data:
|
class Data:
|
||||||
def __init__(self, cfg: CoreConfig) -> None:
|
def __init__(self, cfg: CoreConfig) -> None:
|
||||||
self.config = cfg
|
self.config = cfg
|
||||||
@@ -38,9 +39,13 @@ class Data:
|
|||||||
self.logger = logging.getLogger("database")
|
self.logger = logging.getLogger("database")
|
||||||
|
|
||||||
# Prevent the logger from adding handlers multiple times
|
# Prevent the logger from adding handlers multiple times
|
||||||
if not getattr(self.logger, 'handler_set', None):
|
if not getattr(self.logger, "handler_set", None):
|
||||||
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.config.server.log_dir, "db"), encoding="utf-8",
|
fileHandler = TimedRotatingFileHandler(
|
||||||
when="d", backupCount=10)
|
"{0}/{1}.log".format(self.config.server.log_dir, "db"),
|
||||||
|
encoding="utf-8",
|
||||||
|
when="d",
|
||||||
|
backupCount=10,
|
||||||
|
)
|
||||||
fileHandler.setFormatter(log_fmt)
|
fileHandler.setFormatter(log_fmt)
|
||||||
|
|
||||||
consoleHandler = logging.StreamHandler()
|
consoleHandler = logging.StreamHandler()
|
||||||
@@ -50,7 +55,9 @@ class Data:
|
|||||||
self.logger.addHandler(consoleHandler)
|
self.logger.addHandler(consoleHandler)
|
||||||
|
|
||||||
self.logger.setLevel(self.config.database.loglevel)
|
self.logger.setLevel(self.config.database.loglevel)
|
||||||
coloredlogs.install(cfg.database.loglevel, logger=self.logger, fmt=log_fmt_str)
|
coloredlogs.install(
|
||||||
|
cfg.database.loglevel, logger=self.logger, fmt=log_fmt_str
|
||||||
|
)
|
||||||
self.logger.handler_set = True # type: ignore
|
self.logger.handler_set = True # type: ignore
|
||||||
|
|
||||||
def create_database(self):
|
def create_database(self):
|
||||||
@@ -67,16 +74,24 @@ class Data:
|
|||||||
title_db = game_mod.database(self.config)
|
title_db = game_mod.database(self.config)
|
||||||
metadata.create_all(self.__engine.connect())
|
metadata.create_all(self.__engine.connect())
|
||||||
|
|
||||||
self.base.set_schema_ver(game_mod.current_schema_version, game_mod.game_codes[0])
|
self.base.set_schema_ver(
|
||||||
|
game_mod.current_schema_version, game_mod.game_codes[0]
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.warning(f"Could not load database schema from {game_dir} - {e}")
|
self.logger.warning(
|
||||||
|
f"Could not load database schema from {game_dir} - {e}"
|
||||||
|
)
|
||||||
|
|
||||||
self.logger.info(f"Setting base_schema_ver to {self.schema_ver_latest}")
|
self.logger.info(f"Setting base_schema_ver to {self.schema_ver_latest}")
|
||||||
self.base.set_schema_ver(self.schema_ver_latest)
|
self.base.set_schema_ver(self.schema_ver_latest)
|
||||||
|
|
||||||
self.logger.info(f"Setting user auto_incrememnt to {self.config.database.user_table_autoincrement_start}")
|
self.logger.info(
|
||||||
self.user.reset_autoincrement(self.config.database.user_table_autoincrement_start)
|
f"Setting user auto_incrememnt to {self.config.database.user_table_autoincrement_start}"
|
||||||
|
)
|
||||||
|
self.user.reset_autoincrement(
|
||||||
|
self.config.database.user_table_autoincrement_start
|
||||||
|
)
|
||||||
|
|
||||||
def recreate_database(self):
|
def recreate_database(self):
|
||||||
self.logger.info("Dropping all databases...")
|
self.logger.info("Dropping all databases...")
|
||||||
@@ -98,10 +113,14 @@ class Data:
|
|||||||
metadata.drop_all(self.__engine.connect())
|
metadata.drop_all(self.__engine.connect())
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.warning(f"Could not load database schema from {dir} - {e}")
|
self.logger.warning(
|
||||||
|
f"Could not load database schema from {dir} - {e}"
|
||||||
|
)
|
||||||
|
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
self.logger.warning(f"Failed to load database schema dir {dir} - {e}")
|
self.logger.warning(
|
||||||
|
f"Failed to load database schema dir {dir} - {e}"
|
||||||
|
)
|
||||||
break
|
break
|
||||||
|
|
||||||
self.base.execute("SET FOREIGN_KEY_CHECKS=1")
|
self.base.execute("SET FOREIGN_KEY_CHECKS=1")
|
||||||
@@ -113,18 +132,30 @@ class Data:
|
|||||||
sql = ""
|
sql = ""
|
||||||
|
|
||||||
if old_ver is None:
|
if old_ver is None:
|
||||||
self.logger.error(f"Schema for game {game} does not exist, did you run the creation script?")
|
self.logger.error(
|
||||||
|
f"Schema for game {game} does not exist, did you run the creation script?"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if old_ver == version:
|
if old_ver == version:
|
||||||
self.logger.info(f"Schema for game {game} is already version {old_ver}, nothing to do")
|
self.logger.info(
|
||||||
|
f"Schema for game {game} is already version {old_ver}, nothing to do"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if not os.path.exists(f"core/data/schema/versions/{game.upper()}_{version}_{action}.sql"):
|
if not os.path.exists(
|
||||||
self.logger.error(f"Could not find {action} script {game.upper()}_{version}_{action}.sql in core/data/schema/versions folder")
|
f"core/data/schema/versions/{game.upper()}_{version}_{action}.sql"
|
||||||
|
):
|
||||||
|
self.logger.error(
|
||||||
|
f"Could not find {action} script {game.upper()}_{version}_{action}.sql in core/data/schema/versions folder"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
with open(f"core/data/schema/versions/{game.upper()}_{version}_{action}.sql", "r", encoding="utf-8") as f:
|
with open(
|
||||||
|
f"core/data/schema/versions/{game.upper()}_{version}_{action}.sql",
|
||||||
|
"r",
|
||||||
|
encoding="utf-8",
|
||||||
|
) as f:
|
||||||
sql = f.read()
|
sql = f.read()
|
||||||
|
|
||||||
result = self.base.execute(sql)
|
result = self.base.execute(sql)
|
||||||
@@ -140,7 +171,9 @@ class Data:
|
|||||||
self.logger.info(f"Successfully migrated {game} to schema version {version}")
|
self.logger.info(f"Successfully migrated {game} to schema version {version}")
|
||||||
|
|
||||||
def create_owner(self, email: Optional[str] = None) -> None:
|
def create_owner(self, email: Optional[str] = None) -> None:
|
||||||
pw = ''.join(secrets.choice(string.ascii_letters + string.digits) for i in range(20))
|
pw = "".join(
|
||||||
|
secrets.choice(string.ascii_letters + string.digits) for i in range(20)
|
||||||
|
)
|
||||||
hash = bcrypt.hashpw(pw.encode(), bcrypt.gensalt())
|
hash = bcrypt.hashpw(pw.encode(), bcrypt.gensalt())
|
||||||
|
|
||||||
user_id = self.user.create_user(email=email, permission=255, password=hash)
|
user_id = self.user.create_user(email=email, permission=255, password=hash)
|
||||||
@@ -153,7 +186,9 @@ class Data:
|
|||||||
self.logger.error(f"Failed to create card for owner with id {user_id}")
|
self.logger.error(f"Failed to create card for owner with id {user_id}")
|
||||||
return
|
return
|
||||||
|
|
||||||
self.logger.warn(f"Successfully created owner with email {email}, access code 00000000000000000000, and password {pw} Make sure to change this password and assign a real card ASAP!")
|
self.logger.warn(
|
||||||
|
f"Successfully created owner with email {email}, access code 00000000000000000000, and password {pw} Make sure to change this password and assign a real card ASAP!"
|
||||||
|
)
|
||||||
|
|
||||||
def migrate_card(self, old_ac: str, new_ac: str, should_force: bool) -> None:
|
def migrate_card(self, old_ac: str, new_ac: str, should_force: bool) -> None:
|
||||||
if old_ac == new_ac:
|
if old_ac == new_ac:
|
||||||
@@ -166,18 +201,22 @@ class Data:
|
|||||||
return
|
return
|
||||||
|
|
||||||
if not should_force:
|
if not should_force:
|
||||||
self.logger.warn(f"Card already exists for access code {new_ac} (id {new_card['id']}). If you wish to continue, rerun with the '--force' flag."\
|
self.logger.warn(
|
||||||
f" All exiting data on the target card {new_ac} will be perminently erased and replaced with data from card {old_ac}.")
|
f"Card already exists for access code {new_ac} (id {new_card['id']}). If you wish to continue, rerun with the '--force' flag."
|
||||||
|
f" All exiting data on the target card {new_ac} will be perminently erased and replaced with data from card {old_ac}."
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.logger.info(f"All exiting data on the target card {new_ac} will be perminently erased and replaced with data from card {old_ac}.")
|
self.logger.info(
|
||||||
|
f"All exiting data on the target card {new_ac} will be perminently erased and replaced with data from card {old_ac}."
|
||||||
|
)
|
||||||
self.card.delete_card(new_card["id"])
|
self.card.delete_card(new_card["id"])
|
||||||
self.card.update_access_code(old_ac, new_ac)
|
self.card.update_access_code(old_ac, new_ac)
|
||||||
|
|
||||||
hanging_user = self.user.get_user(new_card["user"])
|
hanging_user = self.user.get_user(new_card["user"])
|
||||||
if hanging_user["password"] is None:
|
if hanging_user["password"] is None:
|
||||||
self.logger.info(f"Delete hanging user {hanging_user['id']}")
|
self.logger.info(f"Delete hanging user {hanging_user['id']}")
|
||||||
self.user.delete_user(hanging_user['id'])
|
self.user.delete_user(hanging_user["id"])
|
||||||
|
|
||||||
def delete_hanging_users(self) -> None:
|
def delete_hanging_users(self) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -188,11 +227,11 @@ class Data:
|
|||||||
self.logger.error("Error occoured finding unregistered users")
|
self.logger.error("Error occoured finding unregistered users")
|
||||||
|
|
||||||
for user in unreg_users:
|
for user in unreg_users:
|
||||||
cards = self.card.get_user_cards(user['id'])
|
cards = self.card.get_user_cards(user["id"])
|
||||||
if cards is None:
|
if cards is None:
|
||||||
self.logger.error(f"Error getting cards for user {user['id']}")
|
self.logger.error(f"Error getting cards for user {user['id']}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not cards:
|
if not cards:
|
||||||
self.logger.info(f"Delete hanging user {user['id']}")
|
self.logger.info(f"Delete hanging user {user['id']}")
|
||||||
self.user.delete_user(user['id'])
|
self.user.delete_user(user["id"])
|
||||||
|
|||||||
+103
-44
@@ -21,14 +21,18 @@ arcade = Table(
|
|||||||
Column("city", String(255)),
|
Column("city", String(255)),
|
||||||
Column("region_id", Integer),
|
Column("region_id", Integer),
|
||||||
Column("timezone", String(255)),
|
Column("timezone", String(255)),
|
||||||
mysql_charset='utf8mb4'
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
machine = Table(
|
machine = Table(
|
||||||
"machine",
|
"machine",
|
||||||
metadata,
|
metadata,
|
||||||
Column("id", Integer, primary_key=True, nullable=False),
|
Column("id", Integer, primary_key=True, nullable=False),
|
||||||
Column("arcade", ForeignKey("arcade.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
Column(
|
||||||
|
"arcade",
|
||||||
|
ForeignKey("arcade.id", ondelete="cascade", onupdate="cascade"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
Column("serial", String(15), nullable=False),
|
Column("serial", String(15), nullable=False),
|
||||||
Column("board", String(15)),
|
Column("board", String(15)),
|
||||||
Column("game", String(4)),
|
Column("game", String(4)),
|
||||||
@@ -36,19 +40,30 @@ machine = Table(
|
|||||||
Column("timezone", String(255)),
|
Column("timezone", String(255)),
|
||||||
Column("ota_enable", Boolean),
|
Column("ota_enable", Boolean),
|
||||||
Column("is_cab", Boolean),
|
Column("is_cab", Boolean),
|
||||||
mysql_charset='utf8mb4'
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
arcade_owner = Table(
|
arcade_owner = Table(
|
||||||
'arcade_owner',
|
"arcade_owner",
|
||||||
metadata,
|
metadata,
|
||||||
Column('user', Integer, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
Column(
|
||||||
Column('arcade', Integer, ForeignKey("arcade.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
"user",
|
||||||
Column('permissions', Integer, nullable=False),
|
Integer,
|
||||||
PrimaryKeyConstraint('user', 'arcade', name='arcade_owner_pk'),
|
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||||
mysql_charset='utf8mb4'
|
nullable=False,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
"arcade",
|
||||||
|
Integer,
|
||||||
|
ForeignKey("arcade.id", ondelete="cascade", onupdate="cascade"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
|
Column("permissions", Integer, nullable=False),
|
||||||
|
PrimaryKeyConstraint("user", "arcade", name="arcade_owner_pk"),
|
||||||
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ArcadeData(BaseData):
|
class ArcadeData(BaseData):
|
||||||
def get_machine(self, serial: str = None, id: int = None) -> Optional[Dict]:
|
def get_machine(self, serial: str = None, id: int = None) -> Optional[Dict]:
|
||||||
if serial is not None:
|
if serial is not None:
|
||||||
@@ -71,72 +86,112 @@ class ArcadeData(BaseData):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.fetchone()
|
return result.fetchone()
|
||||||
|
|
||||||
def put_machine(self, arcade_id: int, serial: str = "", board: str = None, game: str = None, is_cab: bool = False) -> Optional[int]:
|
def put_machine(
|
||||||
|
self,
|
||||||
|
arcade_id: int,
|
||||||
|
serial: str = "",
|
||||||
|
board: str = None,
|
||||||
|
game: str = None,
|
||||||
|
is_cab: bool = False,
|
||||||
|
) -> Optional[int]:
|
||||||
if arcade_id:
|
if arcade_id:
|
||||||
self.logger.error(f"{__name__ }: Need arcade id!")
|
self.logger.error(f"{__name__ }: Need arcade id!")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
sql = machine.insert().values(arcade = arcade_id, keychip = serial, board = board, game = game, is_cab = is_cab)
|
sql = machine.insert().values(
|
||||||
|
arcade=arcade_id, keychip=serial, board=board, game=game, is_cab=is_cab
|
||||||
|
)
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
def set_machine_serial(self, machine_id: int, serial: str) -> None:
|
def set_machine_serial(self, machine_id: int, serial: str) -> None:
|
||||||
result = self.execute(machine.update(machine.c.id == machine_id).values(keychip = serial))
|
result = self.execute(
|
||||||
|
machine.update(machine.c.id == machine_id).values(keychip=serial)
|
||||||
|
)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.error(f"Failed to update serial for machine {machine_id} -> {serial}")
|
self.logger.error(
|
||||||
|
f"Failed to update serial for machine {machine_id} -> {serial}"
|
||||||
|
)
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
def set_machine_boardid(self, machine_id: int, boardid: str) -> None:
|
def set_machine_boardid(self, machine_id: int, boardid: str) -> None:
|
||||||
result = self.execute(machine.update(machine.c.id == machine_id).values(board = boardid))
|
result = self.execute(
|
||||||
|
machine.update(machine.c.id == machine_id).values(board=boardid)
|
||||||
|
)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.error(f"Failed to update board id for machine {machine_id} -> {boardid}")
|
self.logger.error(
|
||||||
|
f"Failed to update board id for machine {machine_id} -> {boardid}"
|
||||||
|
)
|
||||||
|
|
||||||
def get_arcade(self, id: int) -> Optional[Dict]:
|
def get_arcade(self, id: int) -> Optional[Dict]:
|
||||||
sql = arcade.select(arcade.c.id == id)
|
sql = arcade.select(arcade.c.id == id)
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.fetchone()
|
return result.fetchone()
|
||||||
|
|
||||||
def put_arcade(self, name: str, nickname: str = None, country: str = "JPN", country_id: int = 1,
|
def put_arcade(
|
||||||
state: str = "", city: str = "", regional_id: int = 1) -> Optional[int]:
|
self,
|
||||||
if nickname is None: nickname = name
|
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,
|
sql = arcade.insert().values(
|
||||||
state = state, city = city, regional_id = regional_id)
|
name=name,
|
||||||
|
nickname=nickname,
|
||||||
result = self.execute(sql)
|
country=country,
|
||||||
if result is None: return None
|
country_id=country_id,
|
||||||
return result.lastrowid
|
state=state,
|
||||||
|
city=city,
|
||||||
def get_arcade_owners(self, arcade_id: int) -> Optional[Dict]:
|
regional_id=regional_id,
|
||||||
sql = select(arcade_owner).where(arcade_owner.c.arcade==arcade_id)
|
|
||||||
|
|
||||||
result = self.execute(sql)
|
|
||||||
if result is None: return None
|
|
||||||
return result.fetchall()
|
|
||||||
|
|
||||||
def add_arcade_owner(self, arcade_id: int, user_id: int) -> None:
|
|
||||||
sql = insert(arcade_owner).values(
|
|
||||||
arcade=arcade_id,
|
|
||||||
user=user_id
|
|
||||||
)
|
)
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
def format_serial(self, platform_code: str, platform_rev: int, serial_num: int, append: int = 4152) -> str:
|
def 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
|
return f"{platform_code}{platform_rev:02d}A{serial_num:04d}{append:04d}" # 0x41 = A, 0x52 = R
|
||||||
|
|
||||||
def validate_keychip_format(self, serial: str) -> bool:
|
def validate_keychip_format(self, serial: str) -> bool:
|
||||||
serial = serial.replace("-", "")
|
serial = serial.replace("-", "")
|
||||||
if len(serial) != 11 or len(serial) != 15:
|
if len(serial) != 11 or len(serial) != 15:
|
||||||
self.logger.error(f"Serial validate failed: Incorrect length for {serial} (len {len(serial)})")
|
self.logger.error(
|
||||||
|
f"Serial validate failed: Incorrect length for {serial} (len {len(serial)})"
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
platform_code = serial[:4]
|
platform_code = serial[:4]
|
||||||
@@ -150,11 +205,15 @@ class ArcadeData(BaseData):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
if len(append) != 0 or len(append) != 4:
|
if len(append) != 0 or len(append) != 4:
|
||||||
self.logger.error(f"Serial validate failed: {serial} had malformed append {append}")
|
self.logger.error(
|
||||||
|
f"Serial validate failed: {serial} had malformed append {append}"
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if len(num) != 4:
|
if len(num) != 4:
|
||||||
self.logger.error(f"Serial validate failed: {serial} had malformed number {num}")
|
self.logger.error(
|
||||||
|
f"Serial validate failed: {serial} had malformed number {num}"
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
+26
-12
@@ -19,7 +19,7 @@ schema_ver = Table(
|
|||||||
metadata,
|
metadata,
|
||||||
Column("game", String(4), primary_key=True, nullable=False),
|
Column("game", String(4), primary_key=True, nullable=False),
|
||||||
Column("version", Integer, nullable=False, server_default="1"),
|
Column("version", Integer, nullable=False, server_default="1"),
|
||||||
mysql_charset='utf8mb4'
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
event_log = Table(
|
event_log = Table(
|
||||||
@@ -32,16 +32,17 @@ event_log = Table(
|
|||||||
Column("message", String(1000), nullable=False),
|
Column("message", String(1000), nullable=False),
|
||||||
Column("details", JSON, nullable=False),
|
Column("details", JSON, nullable=False),
|
||||||
Column("when_logged", TIMESTAMP, nullable=False, server_default=func.now()),
|
Column("when_logged", TIMESTAMP, nullable=False, server_default=func.now()),
|
||||||
mysql_charset='utf8mb4'
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
class BaseData():
|
|
||||||
|
class BaseData:
|
||||||
def __init__(self, cfg: CoreConfig, conn: Connection) -> None:
|
def __init__(self, cfg: CoreConfig, conn: Connection) -> None:
|
||||||
self.config = cfg
|
self.config = cfg
|
||||||
self.conn = conn
|
self.conn = conn
|
||||||
self.logger = logging.getLogger("database")
|
self.logger = logging.getLogger("database")
|
||||||
|
|
||||||
def execute(self, sql: str, opts: Dict[str, Any]={}) -> Optional[CursorResult]:
|
def execute(self, sql: str, opts: Dict[str, Any] = {}) -> Optional[CursorResult]:
|
||||||
res = None
|
res = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -94,21 +95,33 @@ class BaseData():
|
|||||||
return row["version"]
|
return row["version"]
|
||||||
|
|
||||||
def set_schema_ver(self, ver: int, game: str = "CORE") -> Optional[int]:
|
def set_schema_ver(self, ver: int, game: str = "CORE") -> Optional[int]:
|
||||||
sql = insert(schema_ver).values(game = game, version = ver)
|
sql = insert(schema_ver).values(game=game, version=ver)
|
||||||
conflict = sql.on_duplicate_key_update(version = ver)
|
conflict = sql.on_duplicate_key_update(version=ver)
|
||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.error(f"Failed to update schema version for game {game} (v{ver})")
|
self.logger.error(
|
||||||
|
f"Failed to update schema version for game {game} (v{ver})"
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
def log_event(self, system: str, type: str, severity: int, message: str, details: Dict = {}) -> Optional[int]:
|
def log_event(
|
||||||
sql = event_log.insert().values(system = system, type = type, severity = severity, message = message, details = json.dumps(details))
|
self, system: str, type: str, severity: int, message: str, details: Dict = {}
|
||||||
|
) -> Optional[int]:
|
||||||
|
sql = event_log.insert().values(
|
||||||
|
system=system,
|
||||||
|
type=type,
|
||||||
|
severity=severity,
|
||||||
|
message=message,
|
||||||
|
details=json.dumps(details),
|
||||||
|
)
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.error(f"{__name__}: Failed to insert event into event log! system = {system}, type = {type}, severity = {severity}, message = {message}")
|
self.logger.error(
|
||||||
|
f"{__name__}: Failed to insert event into event log! system = {system}, type = {type}, severity = {severity}, message = {message}"
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
@@ -117,11 +130,12 @@ class BaseData():
|
|||||||
sql = event_log.select().limit(entries).all()
|
sql = event_log.select().limit(entries).all()
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
|
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.fetchall()
|
return result.fetchall()
|
||||||
|
|
||||||
def fix_bools(self, data: Dict) -> Dict:
|
def fix_bools(self, data: Dict) -> Dict:
|
||||||
for k,v in data.items():
|
for k, v in data.items():
|
||||||
if type(v) == str and v.lower() == "true":
|
if type(v) == str and v.lower() == "true":
|
||||||
data[k] = True
|
data[k] = True
|
||||||
elif type(v) == str and v.lower() == "false":
|
elif type(v) == str and v.lower() == "false":
|
||||||
|
|||||||
+25
-12
@@ -8,47 +8,59 @@ from sqlalchemy.engine import Row
|
|||||||
from core.data.schema.base import BaseData, metadata
|
from core.data.schema.base import BaseData, metadata
|
||||||
|
|
||||||
aime_card = Table(
|
aime_card = Table(
|
||||||
'aime_card',
|
"aime_card",
|
||||||
metadata,
|
metadata,
|
||||||
Column("id", Integer, primary_key=True, nullable=False),
|
Column("id", Integer, primary_key=True, nullable=False),
|
||||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
Column(
|
||||||
|
"user",
|
||||||
|
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
Column("access_code", String(20)),
|
Column("access_code", String(20)),
|
||||||
Column("created_date", TIMESTAMP, server_default=func.now()),
|
Column("created_date", TIMESTAMP, server_default=func.now()),
|
||||||
Column("last_login_date", TIMESTAMP, onupdate=func.now()),
|
Column("last_login_date", TIMESTAMP, onupdate=func.now()),
|
||||||
Column("is_locked", Boolean, server_default="0"),
|
Column("is_locked", Boolean, server_default="0"),
|
||||||
Column("is_banned", Boolean, server_default="0"),
|
Column("is_banned", Boolean, server_default="0"),
|
||||||
UniqueConstraint("user", "access_code", name="aime_card_uk"),
|
UniqueConstraint("user", "access_code", name="aime_card_uk"),
|
||||||
mysql_charset='utf8mb4'
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CardData(BaseData):
|
class CardData(BaseData):
|
||||||
def get_card_by_access_code(self, access_code: str) -> Optional[Row]:
|
def get_card_by_access_code(self, access_code: str) -> Optional[Row]:
|
||||||
sql = aime_card.select(aime_card.c.access_code == access_code)
|
sql = aime_card.select(aime_card.c.access_code == access_code)
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.fetchone()
|
return result.fetchone()
|
||||||
|
|
||||||
def get_card_by_id(self, card_id: int) -> Optional[Row]:
|
def get_card_by_id(self, card_id: int) -> Optional[Row]:
|
||||||
sql = aime_card.select(aime_card.c.id == card_id)
|
sql = aime_card.select(aime_card.c.id == card_id)
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.fetchone()
|
return result.fetchone()
|
||||||
|
|
||||||
def update_access_code(self, old_ac: str, new_ac: str) -> None:
|
def update_access_code(self, old_ac: str, new_ac: str) -> None:
|
||||||
sql = aime_card.update(aime_card.c.access_code == old_ac).values(access_code = new_ac)
|
sql = aime_card.update(aime_card.c.access_code == old_ac).values(
|
||||||
|
access_code=new_ac
|
||||||
|
)
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.error(f"Failed to change card access code from {old_ac} to {new_ac}")
|
self.logger.error(
|
||||||
|
f"Failed to change card access code from {old_ac} to {new_ac}"
|
||||||
|
)
|
||||||
|
|
||||||
def get_user_id_from_card(self, access_code: str) -> Optional[int]:
|
def get_user_id_from_card(self, access_code: str) -> Optional[int]:
|
||||||
"""
|
"""
|
||||||
Given a 20 digit access code as a string, get the user id associated with that card
|
Given a 20 digit access code as a string, get the user id associated with that card
|
||||||
"""
|
"""
|
||||||
card = self.get_card_by_access_code(access_code)
|
card = self.get_card_by_access_code(access_code)
|
||||||
if card is None: return None
|
if card is None:
|
||||||
|
return None
|
||||||
|
|
||||||
return int(card["user"])
|
return int(card["user"])
|
||||||
|
|
||||||
@@ -65,17 +77,18 @@ class CardData(BaseData):
|
|||||||
"""
|
"""
|
||||||
sql = aime_card.select(aime_card.c.user == aime_id)
|
sql = aime_card.select(aime_card.c.user == aime_id)
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.fetchall()
|
return result.fetchall()
|
||||||
|
|
||||||
|
|
||||||
def create_card(self, user_id: int, access_code: str) -> Optional[int]:
|
def create_card(self, user_id: int, access_code: str) -> Optional[int]:
|
||||||
"""
|
"""
|
||||||
Given a aime_user id and a 20 digit access code as a string, create a card and return the ID if successful
|
Given a aime_user id and a 20 digit access code as a string, create a card and return the ID if successful
|
||||||
"""
|
"""
|
||||||
sql = aime_card.insert().values(user=user_id, access_code=access_code)
|
sql = aime_card.insert().values(user=user_id, access_code=access_code)
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
def to_access_code(self, luid: str) -> str:
|
def to_access_code(self, luid: str) -> str:
|
||||||
@@ -88,4 +101,4 @@ class CardData(BaseData):
|
|||||||
"""
|
"""
|
||||||
Given a 20 digit access code as a string, return the 16 hex character luid
|
Given a 20 digit access code as a string, return the 16 hex character luid
|
||||||
"""
|
"""
|
||||||
return f'{int(access_code):0{16}x}'
|
return f"{int(access_code):0{16}x}"
|
||||||
|
|||||||
+23
-14
@@ -21,22 +21,31 @@ aime_user = Table(
|
|||||||
Column("created_date", TIMESTAMP, server_default=func.now()),
|
Column("created_date", TIMESTAMP, server_default=func.now()),
|
||||||
Column("last_login_date", TIMESTAMP, onupdate=func.now()),
|
Column("last_login_date", TIMESTAMP, onupdate=func.now()),
|
||||||
Column("suspend_expire_time", TIMESTAMP),
|
Column("suspend_expire_time", TIMESTAMP),
|
||||||
mysql_charset='utf8mb4'
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class PermissionBits(Enum):
|
class PermissionBits(Enum):
|
||||||
PermUser = 1
|
PermUser = 1
|
||||||
PermMod = 2
|
PermMod = 2
|
||||||
PermSysAdmin = 4
|
PermSysAdmin = 4
|
||||||
|
|
||||||
|
|
||||||
class UserData(BaseData):
|
class UserData(BaseData):
|
||||||
def create_user(self, id: int = None, username: str = None, email: str = None, password: str = None, permission: int = 1) -> Optional[int]:
|
def create_user(
|
||||||
|
self,
|
||||||
|
id: int = None,
|
||||||
|
username: str = None,
|
||||||
|
email: str = None,
|
||||||
|
password: str = None,
|
||||||
|
permission: int = 1,
|
||||||
|
) -> Optional[int]:
|
||||||
if id is None:
|
if id is None:
|
||||||
sql = insert(aime_user).values(
|
sql = insert(aime_user).values(
|
||||||
username=username,
|
username=username,
|
||||||
email=email,
|
email=email,
|
||||||
password=password,
|
password=password,
|
||||||
permissions=permission
|
permissions=permission,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
sql = insert(aime_user).values(
|
sql = insert(aime_user).values(
|
||||||
@@ -44,34 +53,34 @@ class UserData(BaseData):
|
|||||||
username=username,
|
username=username,
|
||||||
email=email,
|
email=email,
|
||||||
password=password,
|
password=password,
|
||||||
permissions=permission
|
permissions=permission,
|
||||||
)
|
)
|
||||||
|
|
||||||
conflict = sql.on_duplicate_key_update(
|
conflict = sql.on_duplicate_key_update(
|
||||||
username=username,
|
username=username, email=email, password=password, permissions=permission
|
||||||
email=email,
|
|
||||||
password=password,
|
|
||||||
permissions=permission
|
|
||||||
)
|
)
|
||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
def get_user(self, user_id: int) -> Optional[Row]:
|
def get_user(self, user_id: int) -> Optional[Row]:
|
||||||
sql = select(aime_user).where(aime_user.c.id == user_id)
|
sql = select(aime_user).where(aime_user.c.id == user_id)
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return False
|
if result is None:
|
||||||
|
return False
|
||||||
return result.fetchone()
|
return result.fetchone()
|
||||||
|
|
||||||
def check_password(self, user_id: int, passwd: bytes = None) -> bool:
|
def check_password(self, user_id: int, passwd: bytes = None) -> bool:
|
||||||
usr = self.get_user(user_id)
|
usr = self.get_user(user_id)
|
||||||
if usr is None: return False
|
if usr is None:
|
||||||
|
|
||||||
if usr['password'] is None:
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return bcrypt.checkpw(passwd, usr['password'].encode())
|
if usr["password"] is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return bcrypt.checkpw(passwd, usr["password"].encode())
|
||||||
|
|
||||||
def reset_autoincrement(self, ai_value: int) -> None:
|
def reset_autoincrement(self, ai_value: int) -> None:
|
||||||
# ALTER TABLE isn't in sqlalchemy so we do this the ugly way
|
# ALTER TABLE isn't in sqlalchemy so we do this the ugly way
|
||||||
|
|||||||
+54
-17
@@ -14,11 +14,13 @@ from core.config import CoreConfig
|
|||||||
from core.data import Data
|
from core.data import Data
|
||||||
from core.utils import Utils
|
from core.utils import Utils
|
||||||
|
|
||||||
|
|
||||||
class IUserSession(Interface):
|
class IUserSession(Interface):
|
||||||
userId = Attribute("User's ID")
|
userId = Attribute("User's ID")
|
||||||
current_ip = Attribute("User's current ip address")
|
current_ip = Attribute("User's current ip address")
|
||||||
permissions = Attribute("User's permission level")
|
permissions = Attribute("User's permission level")
|
||||||
|
|
||||||
|
|
||||||
@implementer(IUserSession)
|
@implementer(IUserSession)
|
||||||
class UserSession(object):
|
class UserSession(object):
|
||||||
def __init__(self, session):
|
def __init__(self, session):
|
||||||
@@ -26,10 +28,11 @@ class UserSession(object):
|
|||||||
self.current_ip = "0.0.0.0"
|
self.current_ip = "0.0.0.0"
|
||||||
self.permissions = 0
|
self.permissions = 0
|
||||||
|
|
||||||
|
|
||||||
class FrontendServlet(resource.Resource):
|
class FrontendServlet(resource.Resource):
|
||||||
def getChild(self, name: bytes, request: Request):
|
def getChild(self, name: bytes, request: Request):
|
||||||
self.logger.debug(f"{request.getClientIP()} -> {name.decode()}")
|
self.logger.debug(f"{request.getClientIP()} -> {name.decode()}")
|
||||||
if name == b'':
|
if name == b"":
|
||||||
return self
|
return self
|
||||||
return resource.Resource.getChild(self, name, request)
|
return resource.Resource.getChild(self, name, request)
|
||||||
|
|
||||||
@@ -42,7 +45,11 @@ class FrontendServlet(resource.Resource):
|
|||||||
self.game_list: List[Dict[str, str]] = []
|
self.game_list: List[Dict[str, str]] = []
|
||||||
self.children: Dict[str, Any] = {}
|
self.children: Dict[str, Any] = {}
|
||||||
|
|
||||||
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.config.server.log_dir, "frontend"), when="d", backupCount=10)
|
fileHandler = TimedRotatingFileHandler(
|
||||||
|
"{0}/{1}.log".format(self.config.server.log_dir, "frontend"),
|
||||||
|
when="d",
|
||||||
|
backupCount=10,
|
||||||
|
)
|
||||||
fileHandler.setFormatter(log_fmt)
|
fileHandler.setFormatter(log_fmt)
|
||||||
|
|
||||||
consoleHandler = logging.StreamHandler()
|
consoleHandler = logging.StreamHandler()
|
||||||
@@ -52,7 +59,9 @@ class FrontendServlet(resource.Resource):
|
|||||||
self.logger.addHandler(consoleHandler)
|
self.logger.addHandler(consoleHandler)
|
||||||
|
|
||||||
self.logger.setLevel(cfg.frontend.loglevel)
|
self.logger.setLevel(cfg.frontend.loglevel)
|
||||||
coloredlogs.install(level=cfg.frontend.loglevel, logger=self.logger, fmt=log_fmt_str)
|
coloredlogs.install(
|
||||||
|
level=cfg.frontend.loglevel, logger=self.logger, fmt=log_fmt_str
|
||||||
|
)
|
||||||
registerAdapter(UserSession, Session, IUserSession)
|
registerAdapter(UserSession, Session, IUserSession)
|
||||||
|
|
||||||
fe_game = FE_Game(cfg, self.environment)
|
fe_game = FE_Game(cfg, self.environment)
|
||||||
@@ -71,12 +80,20 @@ class FrontendServlet(resource.Resource):
|
|||||||
self.putChild(b"user", FE_User(cfg, self.environment))
|
self.putChild(b"user", FE_User(cfg, self.environment))
|
||||||
self.putChild(b"game", fe_game)
|
self.putChild(b"game", fe_game)
|
||||||
|
|
||||||
self.logger.info(f"Ready on port {self.config.frontend.port} serving {len(fe_game.children)} games")
|
self.logger.info(
|
||||||
|
f"Ready on port {self.config.frontend.port} serving {len(fe_game.children)} games"
|
||||||
|
)
|
||||||
|
|
||||||
def render_GET(self, request):
|
def render_GET(self, request):
|
||||||
self.logger.debug(f"{request.getClientIP()} -> {request.uri.decode()}")
|
self.logger.debug(f"{request.getClientIP()} -> {request.uri.decode()}")
|
||||||
template = self.environment.get_template("core/frontend/index.jinja")
|
template = self.environment.get_template("core/frontend/index.jinja")
|
||||||
return template.render(server_name=self.config.server.name, title=self.config.server.name, game_list=self.game_list, sesh=vars(IUserSession(request.getSession()))).encode("utf-16")
|
return template.render(
|
||||||
|
server_name=self.config.server.name,
|
||||||
|
title=self.config.server.name,
|
||||||
|
game_list=self.game_list,
|
||||||
|
sesh=vars(IUserSession(request.getSession())),
|
||||||
|
).encode("utf-16")
|
||||||
|
|
||||||
|
|
||||||
class FE_Base(resource.Resource):
|
class FE_Base(resource.Resource):
|
||||||
"""
|
"""
|
||||||
@@ -84,14 +101,17 @@ class FE_Base(resource.Resource):
|
|||||||
Initializes the environment, data, logger, config, and sets isLeaf to true
|
Initializes the environment, data, logger, config, and sets isLeaf to true
|
||||||
It is expected that game implementations of this class overwrite many of these
|
It is expected that game implementations of this class overwrite many of these
|
||||||
"""
|
"""
|
||||||
|
|
||||||
isLeaf = True
|
isLeaf = True
|
||||||
|
|
||||||
def __init__(self, cfg: CoreConfig, environment: jinja2.Environment) -> None:
|
def __init__(self, cfg: CoreConfig, environment: jinja2.Environment) -> None:
|
||||||
self.core_config = cfg
|
self.core_config = cfg
|
||||||
self.data = Data(cfg)
|
self.data = Data(cfg)
|
||||||
self.logger = logging.getLogger('frontend')
|
self.logger = logging.getLogger("frontend")
|
||||||
self.environment = environment
|
self.environment = environment
|
||||||
self.nav_name = "nav_name"
|
self.nav_name = "nav_name"
|
||||||
|
|
||||||
|
|
||||||
class FE_Gate(FE_Base):
|
class FE_Gate(FE_Base):
|
||||||
def render_GET(self, request: Request):
|
def render_GET(self, request: Request):
|
||||||
self.logger.debug(f"{request.getClientIP()} -> {request.uri.decode()}")
|
self.logger.debug(f"{request.getClientIP()} -> {request.uri.decode()}")
|
||||||
@@ -105,16 +125,21 @@ class FE_Gate(FE_Base):
|
|||||||
if uri.startswith("/gate/create"):
|
if uri.startswith("/gate/create"):
|
||||||
return self.create_user(request)
|
return self.create_user(request)
|
||||||
|
|
||||||
if b'e' in request.args:
|
if b"e" in request.args:
|
||||||
try:
|
try:
|
||||||
err = int(request.args[b'e'][0].decode())
|
err = int(request.args[b"e"][0].decode())
|
||||||
except:
|
except:
|
||||||
err = 0
|
err = 0
|
||||||
|
|
||||||
else: err = 0
|
else:
|
||||||
|
err = 0
|
||||||
|
|
||||||
template = self.environment.get_template("core/frontend/gate/gate.jinja")
|
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")
|
return template.render(
|
||||||
|
title=f"{self.core_config.server.name} | Login Gate",
|
||||||
|
error=err,
|
||||||
|
sesh=vars(usr_sesh),
|
||||||
|
).encode("utf-16")
|
||||||
|
|
||||||
def render_POST(self, request: Request):
|
def render_POST(self, request: Request):
|
||||||
uri = request.uri.decode()
|
uri = request.uri.decode()
|
||||||
@@ -134,7 +159,9 @@ class FE_Gate(FE_Base):
|
|||||||
sesh = self.data.user.check_password(uid)
|
sesh = self.data.user.check_password(uid)
|
||||||
|
|
||||||
if sesh is not None:
|
if sesh is not None:
|
||||||
return redirectTo(f"/gate/create?ac={access_code}".encode(), request)
|
return redirectTo(
|
||||||
|
f"/gate/create?ac={access_code}".encode(), request
|
||||||
|
)
|
||||||
return redirectTo(b"/gate?e=1", request)
|
return redirectTo(b"/gate?e=1", request)
|
||||||
|
|
||||||
if not self.data.user.check_password(uid, passwd):
|
if not self.data.user.check_password(uid, passwd):
|
||||||
@@ -162,7 +189,9 @@ class FE_Gate(FE_Base):
|
|||||||
salt = bcrypt.gensalt()
|
salt = bcrypt.gensalt()
|
||||||
hashed = bcrypt.hashpw(passwd, salt)
|
hashed = bcrypt.hashpw(passwd, salt)
|
||||||
|
|
||||||
result = self.data.user.create_user(uid, username, email, hashed.decode(), 1)
|
result = self.data.user.create_user(
|
||||||
|
uid, username, email, hashed.decode(), 1
|
||||||
|
)
|
||||||
if result is None:
|
if result is None:
|
||||||
return redirectTo(b"/gate?e=3", request)
|
return redirectTo(b"/gate?e=3", request)
|
||||||
|
|
||||||
@@ -175,13 +204,18 @@ class FE_Gate(FE_Base):
|
|||||||
return b""
|
return b""
|
||||||
|
|
||||||
def create_user(self, request: Request):
|
def create_user(self, request: Request):
|
||||||
if b'ac' not in request.args or len(request.args[b'ac'][0].decode()) != 20:
|
if b"ac" not in request.args or len(request.args[b"ac"][0].decode()) != 20:
|
||||||
return redirectTo(b"/gate?e=2", request)
|
return redirectTo(b"/gate?e=2", request)
|
||||||
|
|
||||||
ac = request.args[b'ac'][0].decode()
|
ac = request.args[b"ac"][0].decode()
|
||||||
|
|
||||||
template = self.environment.get_template("core/frontend/gate/create.jinja")
|
template = self.environment.get_template("core/frontend/gate/create.jinja")
|
||||||
return template.render(title=f"{self.core_config.server.name} | Create User", code=ac, sesh={"userId": 0}).encode("utf-16")
|
return template.render(
|
||||||
|
title=f"{self.core_config.server.name} | Create User",
|
||||||
|
code=ac,
|
||||||
|
sesh={"userId": 0},
|
||||||
|
).encode("utf-16")
|
||||||
|
|
||||||
|
|
||||||
class FE_User(FE_Base):
|
class FE_User(FE_Base):
|
||||||
def render_GET(self, request: Request):
|
def render_GET(self, request: Request):
|
||||||
@@ -192,14 +226,17 @@ class FE_User(FE_Base):
|
|||||||
if usr_sesh.userId == 0:
|
if usr_sesh.userId == 0:
|
||||||
return redirectTo(b"/gate", request)
|
return redirectTo(b"/gate", request)
|
||||||
|
|
||||||
return template.render(title=f"{self.core_config.server.name} | Account", sesh=vars(usr_sesh)).encode("utf-16")
|
return template.render(
|
||||||
|
title=f"{self.core_config.server.name} | Account", sesh=vars(usr_sesh)
|
||||||
|
).encode("utf-16")
|
||||||
|
|
||||||
|
|
||||||
class FE_Game(FE_Base):
|
class FE_Game(FE_Base):
|
||||||
isLeaf = False
|
isLeaf = False
|
||||||
children: Dict[str, Any] = {}
|
children: Dict[str, Any] = {}
|
||||||
|
|
||||||
def getChild(self, name: bytes, request: Request):
|
def getChild(self, name: bytes, request: Request):
|
||||||
if name == b'':
|
if name == b"":
|
||||||
return self
|
return self
|
||||||
return resource.Resource.getChild(self, name, request)
|
return resource.Resource.getChild(self, name, request)
|
||||||
|
|
||||||
|
|||||||
+62
-37
@@ -9,17 +9,22 @@ import pytz
|
|||||||
from core.config import CoreConfig
|
from core.config import CoreConfig
|
||||||
from core.utils import Utils
|
from core.utils import Utils
|
||||||
|
|
||||||
|
|
||||||
class MuchaServlet:
|
class MuchaServlet:
|
||||||
def __init__(self, cfg: CoreConfig, cfg_dir: str) -> None:
|
def __init__(self, cfg: CoreConfig, cfg_dir: str) -> None:
|
||||||
self.config = cfg
|
self.config = cfg
|
||||||
self.config_dir = cfg_dir
|
self.config_dir = cfg_dir
|
||||||
self.mucha_registry: List[str] = []
|
self.mucha_registry: List[str] = []
|
||||||
|
|
||||||
self.logger = logging.getLogger('mucha')
|
self.logger = logging.getLogger("mucha")
|
||||||
log_fmt_str = "[%(asctime)s] Mucha | %(levelname)s | %(message)s"
|
log_fmt_str = "[%(asctime)s] Mucha | %(levelname)s | %(message)s"
|
||||||
log_fmt = logging.Formatter(log_fmt_str)
|
log_fmt = logging.Formatter(log_fmt_str)
|
||||||
|
|
||||||
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.config.server.log_dir, "mucha"), when="d", backupCount=10)
|
fileHandler = TimedRotatingFileHandler(
|
||||||
|
"{0}/{1}.log".format(self.config.server.log_dir, "mucha"),
|
||||||
|
when="d",
|
||||||
|
backupCount=10,
|
||||||
|
)
|
||||||
fileHandler.setFormatter(log_fmt)
|
fileHandler.setFormatter(log_fmt)
|
||||||
|
|
||||||
consoleHandler = logging.StreamHandler()
|
consoleHandler = logging.StreamHandler()
|
||||||
@@ -35,21 +40,29 @@ class MuchaServlet:
|
|||||||
|
|
||||||
for _, mod in all_titles.items():
|
for _, mod in all_titles.items():
|
||||||
if hasattr(mod, "index") and hasattr(mod.index, "get_mucha_info"):
|
if hasattr(mod, "index") and hasattr(mod.index, "get_mucha_info"):
|
||||||
enabled, game_cd = mod.index.get_mucha_info(self.config, self.config_dir)
|
enabled, game_cd = mod.index.get_mucha_info(
|
||||||
|
self.config, self.config_dir
|
||||||
|
)
|
||||||
if enabled:
|
if enabled:
|
||||||
self.mucha_registry.append(game_cd)
|
self.mucha_registry.append(game_cd)
|
||||||
|
|
||||||
self.logger.info(f"Serving {len(self.mucha_registry)} games on port {self.config.mucha.port}")
|
self.logger.info(
|
||||||
|
f"Serving {len(self.mucha_registry)} games on port {self.config.mucha.port}"
|
||||||
|
)
|
||||||
|
|
||||||
def handle_boardauth(self, request: Request, _: Dict) -> bytes:
|
def handle_boardauth(self, request: Request, _: Dict) -> bytes:
|
||||||
req_dict = self.mucha_preprocess(request.content.getvalue())
|
req_dict = self.mucha_preprocess(request.content.getvalue())
|
||||||
if req_dict is None:
|
if req_dict is None:
|
||||||
self.logger.error(f"Error processing mucha request {request.content.getvalue()}")
|
self.logger.error(
|
||||||
|
f"Error processing mucha request {request.content.getvalue()}"
|
||||||
|
)
|
||||||
return b""
|
return b""
|
||||||
|
|
||||||
req = MuchaAuthRequest(req_dict)
|
req = MuchaAuthRequest(req_dict)
|
||||||
self.logger.debug(f"Mucha request {vars(req)}")
|
self.logger.debug(f"Mucha request {vars(req)}")
|
||||||
self.logger.info(f"Boardauth request from {request.getClientAddress().host} for {req.gameVer}")
|
self.logger.info(
|
||||||
|
f"Boardauth request from {request.getClientAddress().host} for {req.gameVer}"
|
||||||
|
)
|
||||||
|
|
||||||
if req.gameCd not in self.mucha_registry:
|
if req.gameCd not in self.mucha_registry:
|
||||||
self.logger.warn(f"Unknown gameCd {req.gameCd}")
|
self.logger.warn(f"Unknown gameCd {req.gameCd}")
|
||||||
@@ -57,7 +70,9 @@ class MuchaServlet:
|
|||||||
|
|
||||||
# TODO: Decrypt S/N
|
# TODO: Decrypt S/N
|
||||||
|
|
||||||
resp = MuchaAuthResponse(f"{self.config.mucha.hostname}{':' + self.config.mucha.port if self.config.server.is_develop else ''}")
|
resp = MuchaAuthResponse(
|
||||||
|
f"{self.config.mucha.hostname}{':' + str(self.config.mucha.port) if self.config.server.is_develop else ''}"
|
||||||
|
)
|
||||||
|
|
||||||
self.logger.debug(f"Mucha response {vars(resp)}")
|
self.logger.debug(f"Mucha response {vars(resp)}")
|
||||||
|
|
||||||
@@ -66,12 +81,16 @@ class MuchaServlet:
|
|||||||
def handle_updatecheck(self, request: Request, _: Dict) -> bytes:
|
def handle_updatecheck(self, request: Request, _: Dict) -> bytes:
|
||||||
req_dict = self.mucha_preprocess(request.content.getvalue())
|
req_dict = self.mucha_preprocess(request.content.getvalue())
|
||||||
if req_dict is None:
|
if req_dict is None:
|
||||||
self.logger.error(f"Error processing mucha request {request.content.getvalue()}")
|
self.logger.error(
|
||||||
|
f"Error processing mucha request {request.content.getvalue()}"
|
||||||
|
)
|
||||||
return b""
|
return b""
|
||||||
|
|
||||||
req = MuchaUpdateRequest(req_dict)
|
req = MuchaUpdateRequest(req_dict)
|
||||||
self.logger.debug(f"Mucha request {vars(req)}")
|
self.logger.debug(f"Mucha request {vars(req)}")
|
||||||
self.logger.info(f"Updatecheck request from {request.getClientAddress().host} for {req.gameVer}")
|
self.logger.info(
|
||||||
|
f"Updatecheck request from {request.getClientAddress().host} for {req.gameVer}"
|
||||||
|
)
|
||||||
|
|
||||||
if req.gameCd not in self.mucha_registry:
|
if req.gameCd not in self.mucha_registry:
|
||||||
self.logger.warn(f"Unknown gameCd {req.gameCd}")
|
self.logger.warn(f"Unknown gameCd {req.gameCd}")
|
||||||
@@ -87,8 +106,8 @@ class MuchaServlet:
|
|||||||
try:
|
try:
|
||||||
ret: Dict[str, Any] = {}
|
ret: Dict[str, Any] = {}
|
||||||
|
|
||||||
for x in data.decode().split('&'):
|
for x in data.decode().split("&"):
|
||||||
kvp = x.split('=')
|
kvp = x.split("=")
|
||||||
if len(kvp) == 2:
|
if len(kvp) == 2:
|
||||||
ret[kvp[0]] = kvp[1]
|
ret[kvp[0]] = kvp[1]
|
||||||
|
|
||||||
@@ -101,7 +120,7 @@ class MuchaServlet:
|
|||||||
def mucha_postprocess(self, data: dict) -> Optional[bytes]:
|
def mucha_postprocess(self, data: dict) -> Optional[bytes]:
|
||||||
try:
|
try:
|
||||||
urlencode = ""
|
urlencode = ""
|
||||||
for k,v in data.items():
|
for k, v in data.items():
|
||||||
urlencode += f"{k}={v}&"
|
urlencode += f"{k}={v}&"
|
||||||
|
|
||||||
return urlencode.encode()
|
return urlencode.encode()
|
||||||
@@ -110,22 +129,25 @@ class MuchaServlet:
|
|||||||
self.logger.error("Error processing mucha response")
|
self.logger.error("Error processing mucha response")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
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.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.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 MuchaAuthRequest:
|
||||||
|
def __init__(self, request: Dict) -> None:
|
||||||
|
# gameCd + boardType + countryCd + version
|
||||||
|
self.gameVer = request.get("gameVer", "")
|
||||||
|
self.sendDate = request.get("sendDate", "") # %Y%m%d
|
||||||
|
self.serialNum = request.get("serialNum", "")
|
||||||
|
self.gameCd = request.get("gameCd", "")
|
||||||
|
self.boardType = request.get("boardType", "")
|
||||||
|
self.boardId = request.get("boardId", "")
|
||||||
|
self.mac = request.get("mac", "")
|
||||||
|
self.placeId = request.get("placeId", "")
|
||||||
|
self.storeRouterIp = request.get("storeRouterIp", "")
|
||||||
|
self.countryCd = request.get("countryCd", "")
|
||||||
|
self.useToken = request.get("useToken", "")
|
||||||
|
self.allToken = request.get("allToken", "")
|
||||||
|
|
||||||
|
|
||||||
|
class MuchaAuthResponse:
|
||||||
def __init__(self, mucha_url: str) -> None:
|
def __init__(self, mucha_url: str) -> None:
|
||||||
self.RESULTS = "001"
|
self.RESULTS = "001"
|
||||||
self.AUTH_INTERVAL = "86400"
|
self.AUTH_INTERVAL = "86400"
|
||||||
@@ -169,16 +191,18 @@ class MuchaAuthResponse():
|
|||||||
self.DONGLE_FLG = "1"
|
self.DONGLE_FLG = "1"
|
||||||
self.FORCE_BOOT = "0"
|
self.FORCE_BOOT = "0"
|
||||||
|
|
||||||
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"]
|
|
||||||
|
|
||||||
class MuchaUpdateResponse():
|
class MuchaUpdateRequest:
|
||||||
|
def __init__(self, request: Dict) -> None:
|
||||||
|
self.gameVer = request.get("gameVer", "")
|
||||||
|
self.gameCd = request.get("gameCd", "")
|
||||||
|
self.serialNum = request.get("serialNum", "")
|
||||||
|
self.countryCd = request.get("countryCd", "")
|
||||||
|
self.placeId = request.get("placeId", "")
|
||||||
|
self.storeRouterIp = request.get("storeRouterIp", "")
|
||||||
|
|
||||||
|
|
||||||
|
class MuchaUpdateResponse:
|
||||||
def __init__(self, game_ver: str, mucha_url: str) -> None:
|
def __init__(self, game_ver: str, mucha_url: str) -> None:
|
||||||
self.RESULTS = "001"
|
self.RESULTS = "001"
|
||||||
self.UPDATE_VER_1 = game_ver
|
self.UPDATE_VER_1 = game_ver
|
||||||
@@ -194,7 +218,8 @@ class MuchaUpdateResponse():
|
|||||||
self.USER_ID = ""
|
self.USER_ID = ""
|
||||||
self.PASSWORD = ""
|
self.PASSWORD = ""
|
||||||
|
|
||||||
class MuchaUpdateResponseStub():
|
|
||||||
|
class MuchaUpdateResponseStub:
|
||||||
def __init__(self, game_ver: str) -> None:
|
def __init__(self, game_ver: str) -> None:
|
||||||
self.RESULTS = "001"
|
self.RESULTS = "001"
|
||||||
self.UPDATE_VER_1 = game_ver
|
self.UPDATE_VER_1 = game_ver
|
||||||
|
|||||||
+19
-6
@@ -7,7 +7,8 @@ from core.config import CoreConfig
|
|||||||
from core.data import Data
|
from core.data import Data
|
||||||
from core.utils import Utils
|
from core.utils import Utils
|
||||||
|
|
||||||
class TitleServlet():
|
|
||||||
|
class TitleServlet:
|
||||||
def __init__(self, core_cfg: CoreConfig, cfg_folder: str):
|
def __init__(self, core_cfg: CoreConfig, cfg_folder: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.config = core_cfg
|
self.config = core_cfg
|
||||||
@@ -20,7 +21,11 @@ class TitleServlet():
|
|||||||
log_fmt_str = "[%(asctime)s] Title | %(levelname)s | %(message)s"
|
log_fmt_str = "[%(asctime)s] Title | %(levelname)s | %(message)s"
|
||||||
log_fmt = logging.Formatter(log_fmt_str)
|
log_fmt = logging.Formatter(log_fmt_str)
|
||||||
|
|
||||||
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.config.server.log_dir, "title"), when="d", backupCount=10)
|
fileHandler = TimedRotatingFileHandler(
|
||||||
|
"{0}/{1}.log".format(self.config.server.log_dir, "title"),
|
||||||
|
when="d",
|
||||||
|
backupCount=10,
|
||||||
|
)
|
||||||
fileHandler.setFormatter(log_fmt)
|
fileHandler.setFormatter(log_fmt)
|
||||||
|
|
||||||
consoleHandler = logging.StreamHandler()
|
consoleHandler = logging.StreamHandler()
|
||||||
@@ -30,7 +35,9 @@ class TitleServlet():
|
|||||||
self.logger.addHandler(consoleHandler)
|
self.logger.addHandler(consoleHandler)
|
||||||
|
|
||||||
self.logger.setLevel(core_cfg.title.loglevel)
|
self.logger.setLevel(core_cfg.title.loglevel)
|
||||||
coloredlogs.install(level=core_cfg.title.loglevel, logger=self.logger, fmt=log_fmt_str)
|
coloredlogs.install(
|
||||||
|
level=core_cfg.title.loglevel, logger=self.logger, fmt=log_fmt_str
|
||||||
|
)
|
||||||
self.logger.initialized = True
|
self.logger.initialized = True
|
||||||
|
|
||||||
plugins = Utils.get_all_titles()
|
plugins = Utils.get_all_titles()
|
||||||
@@ -41,7 +48,9 @@ class TitleServlet():
|
|||||||
|
|
||||||
if hasattr(mod.index, "get_allnet_info"):
|
if hasattr(mod.index, "get_allnet_info"):
|
||||||
for code in mod.game_codes:
|
for code in mod.game_codes:
|
||||||
enabled, _, _ = mod.index.get_allnet_info(code, self.config, self.config_folder)
|
enabled, _, _ = mod.index.get_allnet_info(
|
||||||
|
code, self.config, self.config_folder
|
||||||
|
)
|
||||||
|
|
||||||
if enabled:
|
if enabled:
|
||||||
handler_cls = mod.index(self.config, self.config_folder)
|
handler_cls = mod.index(self.config, self.config_folder)
|
||||||
@@ -58,7 +67,9 @@ class TitleServlet():
|
|||||||
else:
|
else:
|
||||||
self.logger.error(f"{folder} missing game_code or index in __init__.py")
|
self.logger.error(f"{folder} missing game_code or index in __init__.py")
|
||||||
|
|
||||||
self.logger.info(f"Serving {len(self.title_registry)} game codes on port {core_cfg.title.port}")
|
self.logger.info(
|
||||||
|
f"Serving {len(self.title_registry)} game codes on port {core_cfg.title.port}"
|
||||||
|
)
|
||||||
|
|
||||||
def render_GET(self, request: Request, endpoints: dict) -> bytes:
|
def render_GET(self, request: Request, endpoints: dict) -> bytes:
|
||||||
code = endpoints["game"]
|
code = endpoints["game"]
|
||||||
@@ -88,4 +99,6 @@ class TitleServlet():
|
|||||||
request.setResponseCode(405)
|
request.setResponseCode(405)
|
||||||
return b""
|
return b""
|
||||||
|
|
||||||
return index.render_POST(request, int(endpoints["version"]), endpoints["endpoint"])
|
return index.render_POST(
|
||||||
|
request, int(endpoints["version"]), endpoints["endpoint"]
|
||||||
|
)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import logging
|
|||||||
import importlib
|
import importlib
|
||||||
from os import walk
|
from os import walk
|
||||||
|
|
||||||
|
|
||||||
class Utils:
|
class Utils:
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_all_titles(cls) -> Dict[str, ModuleType]:
|
def get_all_titles(cls) -> Dict[str, ModuleType]:
|
||||||
|
|||||||
+19
-5
@@ -4,16 +4,30 @@ from core.config import CoreConfig
|
|||||||
from core.data import Data
|
from core.data import Data
|
||||||
from os import path
|
from os import path
|
||||||
|
|
||||||
if __name__=='__main__':
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser(description="Database utilities")
|
parser = argparse.ArgumentParser(description="Database utilities")
|
||||||
parser.add_argument("--config", "-c", type=str, help="Config folder to use", default="config")
|
parser.add_argument(
|
||||||
parser.add_argument("--version", "-v", type=str, help="Version of the database to upgrade/rollback to")
|
"--config", "-c", type=str, help="Config folder to use", default="config"
|
||||||
parser.add_argument("--game", "-g", type=str, help="Game code of the game who's schema will be updated/rolled back. Ex. SDFE")
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--version",
|
||||||
|
"-v",
|
||||||
|
type=str,
|
||||||
|
help="Version of the database to upgrade/rollback to",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--game",
|
||||||
|
"-g",
|
||||||
|
type=str,
|
||||||
|
help="Game code of the game who's schema will be updated/rolled back. Ex. SDFE",
|
||||||
|
)
|
||||||
parser.add_argument("--email", "-e", type=str, help="Email for the new user")
|
parser.add_argument("--email", "-e", type=str, help="Email for the new user")
|
||||||
parser.add_argument("--old_ac", "-o", type=str, help="Access code to transfer from")
|
parser.add_argument("--old_ac", "-o", type=str, help="Access code to transfer from")
|
||||||
parser.add_argument("--new_ac", "-n", type=str, help="Access code to transfer to")
|
parser.add_argument("--new_ac", "-n", type=str, help="Access code to transfer to")
|
||||||
parser.add_argument("--force", "-f", type=bool, help="Force the action to happen")
|
parser.add_argument("--force", "-f", type=bool, help="Force the action to happen")
|
||||||
parser.add_argument("action", type=str, help="DB Action, create, recreate, upgrade, or rollback")
|
parser.add_argument(
|
||||||
|
"action", type=str, help="DB Action, create, recreate, upgrade, or rollback"
|
||||||
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
cfg = CoreConfig()
|
cfg = CoreConfig()
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from twisted.internet import reactor, endpoints
|
|||||||
from twisted.web.http import Request
|
from twisted.web.http import Request
|
||||||
from routes import Mapper
|
from routes import Mapper
|
||||||
|
|
||||||
|
|
||||||
class HttpDispatcher(resource.Resource):
|
class HttpDispatcher(resource.Resource):
|
||||||
def __init__(self, cfg: CoreConfig, config_dir: str):
|
def __init__(self, cfg: CoreConfig, config_dir: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -25,22 +26,80 @@ class HttpDispatcher(resource.Resource):
|
|||||||
self.title = TitleServlet(cfg, config_dir)
|
self.title = TitleServlet(cfg, config_dir)
|
||||||
self.mucha = MuchaServlet(cfg, config_dir)
|
self.mucha = MuchaServlet(cfg, config_dir)
|
||||||
|
|
||||||
self.map_post.connect('allnet_ping', '/naomitest.html', controller="allnet", action='handle_naomitest', conditions=dict(method=['GET']))
|
self.map_post.connect(
|
||||||
self.map_post.connect('allnet_poweron', '/sys/servlet/PowerOn', controller="allnet", action='handle_poweron', conditions=dict(method=['POST']))
|
"allnet_ping",
|
||||||
self.map_post.connect('allnet_downloadorder', '/sys/servlet/DownloadOrder', controller="allnet", action='handle_dlorder', conditions=dict(method=['POST']))
|
"/naomitest.html",
|
||||||
self.map_post.connect('allnet_billing', '/request', controller="allnet", action='handle_billing_request', conditions=dict(method=['POST']))
|
controller="allnet",
|
||||||
self.map_post.connect('allnet_billing', '/request/', controller="allnet", action='handle_billing_request', conditions=dict(method=['POST']))
|
action="handle_naomitest",
|
||||||
|
conditions=dict(method=["GET"]),
|
||||||
|
)
|
||||||
|
self.map_post.connect(
|
||||||
|
"allnet_poweron",
|
||||||
|
"/sys/servlet/PowerOn",
|
||||||
|
controller="allnet",
|
||||||
|
action="handle_poweron",
|
||||||
|
conditions=dict(method=["POST"]),
|
||||||
|
)
|
||||||
|
self.map_post.connect(
|
||||||
|
"allnet_downloadorder",
|
||||||
|
"/sys/servlet/DownloadOrder",
|
||||||
|
controller="allnet",
|
||||||
|
action="handle_dlorder",
|
||||||
|
conditions=dict(method=["POST"]),
|
||||||
|
)
|
||||||
|
self.map_post.connect(
|
||||||
|
"allnet_billing",
|
||||||
|
"/request",
|
||||||
|
controller="allnet",
|
||||||
|
action="handle_billing_request",
|
||||||
|
conditions=dict(method=["POST"]),
|
||||||
|
)
|
||||||
|
self.map_post.connect(
|
||||||
|
"allnet_billing",
|
||||||
|
"/request/",
|
||||||
|
controller="allnet",
|
||||||
|
action="handle_billing_request",
|
||||||
|
conditions=dict(method=["POST"]),
|
||||||
|
)
|
||||||
|
|
||||||
self.map_post.connect('mucha_boardauth', '/mucha/boardauth.do', controller="mucha", action='handle_boardauth', conditions=dict(method=['POST']))
|
self.map_post.connect(
|
||||||
self.map_post.connect('mucha_updatacheck', '/mucha/updatacheck.do', controller="mucha", action='handle_updatacheck', conditions=dict(method=['POST']))
|
"mucha_boardauth",
|
||||||
|
"/mucha/boardauth.do",
|
||||||
|
controller="mucha",
|
||||||
|
action="handle_boardauth",
|
||||||
|
conditions=dict(method=["POST"]),
|
||||||
|
)
|
||||||
|
self.map_post.connect(
|
||||||
|
"mucha_updatacheck",
|
||||||
|
"/mucha/updatacheck.do",
|
||||||
|
controller="mucha",
|
||||||
|
action="handle_updatecheck",
|
||||||
|
conditions=dict(method=["POST"]),
|
||||||
|
)
|
||||||
|
|
||||||
self.map_get.connect("title_get", "/{game}/{version}/{endpoint:.*?}", controller="title", action="render_GET", conditions=dict(method=['GET']), requirements=dict(game=R"S..."))
|
self.map_get.connect(
|
||||||
self.map_post.connect("title_post", "/{game}/{version}/{endpoint:.*?}", controller="title", action="render_POST", conditions=dict(method=['POST']), requirements=dict(game=R"S..."))
|
"title_get",
|
||||||
|
"/{game}/{version}/{endpoint:.*?}",
|
||||||
|
controller="title",
|
||||||
|
action="render_GET",
|
||||||
|
conditions=dict(method=["GET"]),
|
||||||
|
requirements=dict(game=R"S..."),
|
||||||
|
)
|
||||||
|
self.map_post.connect(
|
||||||
|
"title_post",
|
||||||
|
"/{game}/{version}/{endpoint:.*?}",
|
||||||
|
controller="title",
|
||||||
|
action="render_POST",
|
||||||
|
conditions=dict(method=["POST"]),
|
||||||
|
requirements=dict(game=R"S..."),
|
||||||
|
)
|
||||||
|
|
||||||
def render_GET(self, request: Request) -> bytes:
|
def render_GET(self, request: Request) -> bytes:
|
||||||
test = self.map_get.match(request.uri.decode())
|
test = self.map_get.match(request.uri.decode())
|
||||||
if test is None:
|
if test is None:
|
||||||
self.logger.debug(f"Unknown GET endpoint {request.uri.decode()} from {request.getClientAddress().host} to port {request.getHost().port}")
|
self.logger.debug(
|
||||||
|
f"Unknown GET endpoint {request.uri.decode()} from {request.getClientAddress().host} to port {request.getHost().port}"
|
||||||
|
)
|
||||||
request.setResponseCode(404)
|
request.setResponseCode(404)
|
||||||
return b"Endpoint not found."
|
return b"Endpoint not found."
|
||||||
|
|
||||||
@@ -49,7 +108,9 @@ class HttpDispatcher(resource.Resource):
|
|||||||
def render_POST(self, request: Request) -> bytes:
|
def render_POST(self, request: Request) -> bytes:
|
||||||
test = self.map_post.match(request.uri.decode())
|
test = self.map_post.match(request.uri.decode())
|
||||||
if test is None:
|
if test is None:
|
||||||
self.logger.debug(f"Unknown POST endpoint {request.uri.decode()} from {request.getClientAddress().host} to port {request.getHost().port}")
|
self.logger.debug(
|
||||||
|
f"Unknown POST endpoint {request.uri.decode()} from {request.getClientAddress().host} to port {request.getHost().port}"
|
||||||
|
)
|
||||||
request.setResponseCode(404)
|
request.setResponseCode(404)
|
||||||
return b"Endpoint not found."
|
return b"Endpoint not found."
|
||||||
|
|
||||||
@@ -58,13 +119,17 @@ class HttpDispatcher(resource.Resource):
|
|||||||
def dispatch(self, matcher: Dict, request: Request) -> bytes:
|
def dispatch(self, matcher: Dict, request: Request) -> bytes:
|
||||||
controller = getattr(self, matcher["controller"], None)
|
controller = getattr(self, matcher["controller"], None)
|
||||||
if controller is None:
|
if controller is None:
|
||||||
self.logger.error(f"Controller {matcher['controller']} not found via endpoint {request.uri.decode()}")
|
self.logger.error(
|
||||||
|
f"Controller {matcher['controller']} not found via endpoint {request.uri.decode()}"
|
||||||
|
)
|
||||||
request.setResponseCode(404)
|
request.setResponseCode(404)
|
||||||
return b"Endpoint not found."
|
return b"Endpoint not found."
|
||||||
|
|
||||||
handler = getattr(controller, matcher["action"], None)
|
handler = getattr(controller, matcher["action"], None)
|
||||||
if handler is None:
|
if handler is None:
|
||||||
self.logger.error(f"Action {matcher['action']} not found in controller {matcher['controller']} via endpoint {request.uri.decode()}")
|
self.logger.error(
|
||||||
|
f"Action {matcher['action']} not found in controller {matcher['controller']} via endpoint {request.uri.decode()}"
|
||||||
|
)
|
||||||
request.setResponseCode(404)
|
request.setResponseCode(404)
|
||||||
return b"Endpoint not found."
|
return b"Endpoint not found."
|
||||||
|
|
||||||
@@ -80,24 +145,34 @@ class HttpDispatcher(resource.Resource):
|
|||||||
else:
|
else:
|
||||||
return b""
|
return b""
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser(description="ARTEMiS main entry point")
|
parser = argparse.ArgumentParser(description="ARTEMiS main entry point")
|
||||||
parser.add_argument("--config", "-c", type=str, default="config", help="Configuration folder")
|
parser.add_argument(
|
||||||
|
"--config", "-c", type=str, default="config", help="Configuration folder"
|
||||||
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if not path.exists(f"{args.config}/core.yaml"):
|
if not path.exists(f"{args.config}/core.yaml"):
|
||||||
print(f"The config folder you specified ({args.config}) does not exist or does not contain core.yaml.\nDid you copy the example folder?")
|
print(
|
||||||
|
f"The config folder you specified ({args.config}) does not exist or does not contain core.yaml.\nDid you copy the example folder?"
|
||||||
|
)
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
cfg: CoreConfig = CoreConfig()
|
cfg: CoreConfig = CoreConfig()
|
||||||
if path.exists(f"{args.config}/core.yaml"):
|
if path.exists(f"{args.config}/core.yaml"):
|
||||||
cfg.update(yaml.safe_load(open(f"{args.config}/core.yaml")))
|
cfg.update(yaml.safe_load(open(f"{args.config}/core.yaml")))
|
||||||
|
|
||||||
|
if not path.exists(cfg.server.log_dir):
|
||||||
|
mkdir(cfg.server.log_dir)
|
||||||
|
|
||||||
logger = logging.getLogger("core")
|
logger = logging.getLogger("core")
|
||||||
log_fmt_str = "[%(asctime)s] Core | %(levelname)s | %(message)s"
|
log_fmt_str = "[%(asctime)s] Core | %(levelname)s | %(message)s"
|
||||||
log_fmt = logging.Formatter(log_fmt_str)
|
log_fmt = logging.Formatter(log_fmt_str)
|
||||||
|
|
||||||
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(cfg.server.log_dir, "core"), when="d", backupCount=10)
|
fileHandler = TimedRotatingFileHandler(
|
||||||
|
"{0}/{1}.log".format(cfg.server.log_dir, "core"), when="d", backupCount=10
|
||||||
|
)
|
||||||
fileHandler.setFormatter(log_fmt)
|
fileHandler.setFormatter(log_fmt)
|
||||||
|
|
||||||
consoleHandler = logging.StreamHandler()
|
consoleHandler = logging.StreamHandler()
|
||||||
@@ -110,41 +185,54 @@ if __name__ == "__main__":
|
|||||||
logger.setLevel(log_lv)
|
logger.setLevel(log_lv)
|
||||||
coloredlogs.install(level=log_lv, logger=logger, fmt=log_fmt_str)
|
coloredlogs.install(level=log_lv, logger=logger, fmt=log_fmt_str)
|
||||||
|
|
||||||
if not path.exists(cfg.server.log_dir):
|
|
||||||
mkdir(cfg.server.log_dir)
|
|
||||||
|
|
||||||
if not access(cfg.server.log_dir, W_OK):
|
if not access(cfg.server.log_dir, W_OK):
|
||||||
logger.error(f"Log directory {cfg.server.log_dir} NOT writable, please check permissions")
|
logger.error(
|
||||||
|
f"Log directory {cfg.server.log_dir} NOT writable, please check permissions"
|
||||||
|
)
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
if not cfg.aimedb.key:
|
if not cfg.aimedb.key:
|
||||||
logger.error("!!AIMEDB KEY BLANK, SET KEY IN CORE.YAML!!")
|
logger.error("!!AIMEDB KEY BLANK, SET KEY IN CORE.YAML!!")
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
logger.info(f"ARTEMiS starting in {'develop' if cfg.server.is_develop else 'production'} mode")
|
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}"
|
allnet_server_str = f"tcp:{cfg.allnet.port}:interface={cfg.server.listen_address}"
|
||||||
title_server_str = f"tcp:{cfg.title.port}:interface={cfg.server.listen_address}"
|
title_server_str = f"tcp:{cfg.title.port}:interface={cfg.server.listen_address}"
|
||||||
adb_server_str = f"tcp:{cfg.aimedb.port}:interface={cfg.server.listen_address}"
|
adb_server_str = f"tcp:{cfg.aimedb.port}:interface={cfg.server.listen_address}"
|
||||||
frontend_server_str = f"tcp:{cfg.frontend.port}:interface={cfg.server.listen_address}"
|
frontend_server_str = (
|
||||||
|
f"tcp:{cfg.frontend.port}:interface={cfg.server.listen_address}"
|
||||||
|
)
|
||||||
|
|
||||||
billing_server_str = f"tcp:{cfg.billing.port}:interface={cfg.server.listen_address}"
|
billing_server_str = f"tcp:{cfg.billing.port}:interface={cfg.server.listen_address}"
|
||||||
if cfg.server.is_develop:
|
if cfg.server.is_develop:
|
||||||
billing_server_str = f"ssl:{cfg.billing.port}:interface={cfg.server.listen_address}"\
|
billing_server_str = (
|
||||||
|
f"ssl:{cfg.billing.port}:interface={cfg.server.listen_address}"
|
||||||
f":privateKey={cfg.billing.ssl_key}:certKey={cfg.billing.ssl_cert}"
|
f":privateKey={cfg.billing.ssl_key}:certKey={cfg.billing.ssl_cert}"
|
||||||
|
)
|
||||||
|
|
||||||
dispatcher = HttpDispatcher(cfg, args.config)
|
dispatcher = HttpDispatcher(cfg, args.config)
|
||||||
|
|
||||||
endpoints.serverFromString(reactor, allnet_server_str).listen(server.Site(dispatcher))
|
endpoints.serverFromString(reactor, allnet_server_str).listen(
|
||||||
|
server.Site(dispatcher)
|
||||||
|
)
|
||||||
endpoints.serverFromString(reactor, adb_server_str).listen(AimedbFactory(cfg))
|
endpoints.serverFromString(reactor, adb_server_str).listen(AimedbFactory(cfg))
|
||||||
|
|
||||||
if cfg.frontend.enable:
|
if cfg.frontend.enable:
|
||||||
endpoints.serverFromString(reactor, frontend_server_str).listen(server.Site(FrontendServlet(cfg, args.config)))
|
endpoints.serverFromString(reactor, frontend_server_str).listen(
|
||||||
|
server.Site(FrontendServlet(cfg, args.config))
|
||||||
|
)
|
||||||
|
|
||||||
if cfg.billing.port > 0:
|
if cfg.billing.port > 0:
|
||||||
endpoints.serverFromString(reactor, billing_server_str).listen(server.Site(dispatcher))
|
endpoints.serverFromString(reactor, billing_server_str).listen(
|
||||||
|
server.Site(dispatcher)
|
||||||
|
)
|
||||||
|
|
||||||
if cfg.title.port > 0:
|
if cfg.title.port > 0:
|
||||||
endpoints.serverFromString(reactor, title_server_str).listen(server.Site(dispatcher))
|
endpoints.serverFromString(reactor, title_server_str).listen(
|
||||||
|
server.Site(dispatcher)
|
||||||
|
)
|
||||||
|
|
||||||
reactor.run() # type: ignore
|
reactor.run() # type: ignore
|
||||||
@@ -12,8 +12,16 @@ from typing import List, Optional
|
|||||||
from core import CoreConfig
|
from core import CoreConfig
|
||||||
from core.utils import Utils
|
from core.utils import Utils
|
||||||
|
|
||||||
class BaseReader():
|
|
||||||
def __init__(self, config: CoreConfig, version: int, bin_dir: Optional[str], opt_dir: Optional[str], extra: Optional[str]) -> None:
|
class BaseReader:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
config: CoreConfig,
|
||||||
|
version: int,
|
||||||
|
bin_dir: Optional[str],
|
||||||
|
opt_dir: Optional[str],
|
||||||
|
extra: Optional[str],
|
||||||
|
) -> None:
|
||||||
self.logger = logging.getLogger("reader")
|
self.logger = logging.getLogger("reader")
|
||||||
self.config = config
|
self.config = config
|
||||||
self.bin_dir = bin_dir
|
self.bin_dir = bin_dir
|
||||||
@@ -21,7 +29,6 @@ class BaseReader():
|
|||||||
self.version = version
|
self.version = version
|
||||||
self.extra = extra
|
self.extra = extra
|
||||||
|
|
||||||
|
|
||||||
def get_data_directories(self, directory: str) -> List[str]:
|
def get_data_directories(self, directory: str) -> List[str]:
|
||||||
ret: List[str] = []
|
ret: List[str] = []
|
||||||
|
|
||||||
@@ -32,36 +39,37 @@ class BaseReader():
|
|||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser(description='Import Game Information')
|
parser = argparse.ArgumentParser(description="Import Game Information")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--series',
|
"--series",
|
||||||
action='store',
|
action="store",
|
||||||
type=str,
|
type=str,
|
||||||
required=True,
|
required=True,
|
||||||
help='The game series we are importing.',
|
help="The game series we are importing.",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--version',
|
"--version",
|
||||||
dest='version',
|
dest="version",
|
||||||
action='store',
|
action="store",
|
||||||
type=int,
|
type=int,
|
||||||
required=True,
|
required=True,
|
||||||
help='The game version we are importing.',
|
help="The game version we are importing.",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--binfolder',
|
"--binfolder",
|
||||||
dest='bin',
|
dest="bin",
|
||||||
action='store',
|
action="store",
|
||||||
type=str,
|
type=str,
|
||||||
help='Folder containing A000 base data',
|
help="Folder containing A000 base data",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--optfolder',
|
"--optfolder",
|
||||||
dest='opt',
|
dest="opt",
|
||||||
action='store',
|
action="store",
|
||||||
type=str,
|
type=str,
|
||||||
help='Folder containing Option data folders',
|
help="Folder containing Option data folders",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--config",
|
"--config",
|
||||||
@@ -86,7 +94,9 @@ if __name__ == "__main__":
|
|||||||
log_fmt = logging.Formatter(log_fmt_str)
|
log_fmt = logging.Formatter(log_fmt_str)
|
||||||
logger = logging.getLogger("reader")
|
logger = logging.getLogger("reader")
|
||||||
|
|
||||||
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(config.server.log_dir, "reader"), when="d", backupCount=10)
|
fileHandler = TimedRotatingFileHandler(
|
||||||
|
"{0}/{1}.log".format(config.server.log_dir, "reader"), when="d", backupCount=10
|
||||||
|
)
|
||||||
fileHandler.setFormatter(log_fmt)
|
fileHandler.setFormatter(log_fmt)
|
||||||
|
|
||||||
consoleHandler = logging.StreamHandler()
|
consoleHandler = logging.StreamHandler()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from titles.chuni.base import ChuniBase
|
|||||||
from titles.chuni.const import ChuniConstants
|
from titles.chuni.const import ChuniConstants
|
||||||
from titles.chuni.config import ChuniConfig
|
from titles.chuni.config import ChuniConfig
|
||||||
|
|
||||||
|
|
||||||
class ChuniAir(ChuniBase):
|
class ChuniAir(ChuniBase):
|
||||||
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
|
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
|
||||||
super().__init__(core_cfg, game_cfg)
|
super().__init__(core_cfg, game_cfg)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from titles.chuni.base import ChuniBase
|
|||||||
from titles.chuni.const import ChuniConstants
|
from titles.chuni.const import ChuniConstants
|
||||||
from titles.chuni.config import ChuniConfig
|
from titles.chuni.config import ChuniConfig
|
||||||
|
|
||||||
|
|
||||||
class ChuniAirPlus(ChuniBase):
|
class ChuniAirPlus(ChuniBase):
|
||||||
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
|
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
|
||||||
super().__init__(core_cfg, game_cfg)
|
super().__init__(core_cfg, game_cfg)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from titles.chuni.base import ChuniBase
|
|||||||
from titles.chuni.const import ChuniConstants
|
from titles.chuni.const import ChuniConstants
|
||||||
from titles.chuni.config import ChuniConfig
|
from titles.chuni.config import ChuniConfig
|
||||||
|
|
||||||
|
|
||||||
class ChuniAmazon(ChuniBase):
|
class ChuniAmazon(ChuniBase):
|
||||||
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
|
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
|
||||||
super().__init__(core_cfg, game_cfg)
|
super().__init__(core_cfg, game_cfg)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from titles.chuni.base import ChuniBase
|
|||||||
from titles.chuni.const import ChuniConstants
|
from titles.chuni.const import ChuniConstants
|
||||||
from titles.chuni.config import ChuniConfig
|
from titles.chuni.config import ChuniConfig
|
||||||
|
|
||||||
|
|
||||||
class ChuniAmazonPlus(ChuniBase):
|
class ChuniAmazonPlus(ChuniBase):
|
||||||
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
|
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
|
||||||
super().__init__(core_cfg, game_cfg)
|
super().__init__(core_cfg, game_cfg)
|
||||||
|
|||||||
+108
-93
@@ -11,7 +11,8 @@ from titles.chuni.const import ChuniConstants
|
|||||||
from titles.chuni.database import ChuniData
|
from titles.chuni.database import ChuniData
|
||||||
from titles.chuni.config import ChuniConfig
|
from titles.chuni.config import ChuniConfig
|
||||||
|
|
||||||
class ChuniBase():
|
|
||||||
|
class ChuniBase:
|
||||||
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
|
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
|
||||||
self.core_cfg = core_cfg
|
self.core_cfg = core_cfg
|
||||||
self.game_cfg = game_cfg
|
self.game_cfg = game_cfg
|
||||||
@@ -21,23 +22,21 @@ class ChuniBase():
|
|||||||
self.game = ChuniConstants.GAME_CODE
|
self.game = ChuniConstants.GAME_CODE
|
||||||
self.version = ChuniConstants.VER_CHUNITHM
|
self.version = ChuniConstants.VER_CHUNITHM
|
||||||
|
|
||||||
def handle_ping_request(self, data: Dict) -> Dict:
|
def handle_game_login_api_request(self, data: Dict) -> Dict:
|
||||||
|
# self.data.base.log_event("chuni", "login", logging.INFO, {"version": self.version, "user": data["userId"]})
|
||||||
return {"returnCode": 1}
|
return {"returnCode": 1}
|
||||||
|
|
||||||
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 }
|
|
||||||
|
|
||||||
def handle_game_logout_api_request(self, data: Dict) -> Dict:
|
def handle_game_logout_api_request(self, data: Dict) -> Dict:
|
||||||
#self.data.base.log_event("chuni", "logout", logging.INFO, {"version": self.version, "user": data["userId"]})
|
# self.data.base.log_event("chuni", "logout", logging.INFO, {"version": self.version, "user": data["userId"]})
|
||||||
return { "returnCode": 1 }
|
return {"returnCode": 1}
|
||||||
|
|
||||||
def handle_get_game_charge_api_request(self, data: Dict) -> Dict:
|
def handle_get_game_charge_api_request(self, data: Dict) -> Dict:
|
||||||
game_charge_list = self.data.static.get_enabled_charges(self.version)
|
game_charge_list = self.data.static.get_enabled_charges(self.version)
|
||||||
|
|
||||||
charges = []
|
charges = []
|
||||||
for x in range(len(game_charge_list)):
|
for x in range(len(game_charge_list)):
|
||||||
charges.append({
|
charges.append(
|
||||||
|
{
|
||||||
"orderId": x,
|
"orderId": x,
|
||||||
"chargeId": game_charge_list[x]["chargeId"],
|
"chargeId": game_charge_list[x]["chargeId"],
|
||||||
"price": 1,
|
"price": 1,
|
||||||
@@ -45,12 +44,10 @@ class ChuniBase():
|
|||||||
"endDate": "2099-12-31 00:00:00.0",
|
"endDate": "2099-12-31 00:00:00.0",
|
||||||
"salePrice": 1,
|
"salePrice": 1,
|
||||||
"saleStartDate": "2017-12-05 07:00:00.0",
|
"saleStartDate": "2017-12-05 07:00:00.0",
|
||||||
"saleEndDate": "2099-12-31 00:00:00.0"
|
"saleEndDate": "2099-12-31 00:00:00.0",
|
||||||
})
|
|
||||||
return {
|
|
||||||
"length": len(charges),
|
|
||||||
"gameChargeList": charges
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
return {"length": len(charges), "gameChargeList": charges}
|
||||||
|
|
||||||
def handle_get_game_event_api_request(self, data: Dict) -> Dict:
|
def handle_get_game_event_api_request(self, data: Dict) -> Dict:
|
||||||
game_events = self.data.static.get_enabled_events(self.version)
|
game_events = self.data.static.get_enabled_events(self.version)
|
||||||
@@ -67,24 +64,28 @@ class ChuniBase():
|
|||||||
return {
|
return {
|
||||||
"type": data["type"],
|
"type": data["type"],
|
||||||
"length": len(event_list),
|
"length": len(event_list),
|
||||||
"gameEventList": event_list
|
"gameEventList": event_list,
|
||||||
}
|
}
|
||||||
|
|
||||||
def handle_get_game_idlist_api_request(self, data: Dict) -> Dict:
|
def handle_get_game_idlist_api_request(self, data: Dict) -> Dict:
|
||||||
return { "type": data["type"], "length": 0, "gameIdlistList": [] }
|
return {"type": data["type"], "length": 0, "gameIdlistList": []}
|
||||||
|
|
||||||
def handle_get_game_message_api_request(self, data: Dict) -> Dict:
|
def handle_get_game_message_api_request(self, data: Dict) -> Dict:
|
||||||
return { "type": data["type"], "length": "0", "gameMessageList": [] }
|
return {"type": data["type"], "length": "0", "gameMessageList": []}
|
||||||
|
|
||||||
def handle_get_game_ranking_api_request(self, data: Dict) -> Dict:
|
def handle_get_game_ranking_api_request(self, data: Dict) -> Dict:
|
||||||
return { "type": data["type"], "gameRankingList": [] }
|
return {"type": data["type"], "gameRankingList": []}
|
||||||
|
|
||||||
def handle_get_game_sale_api_request(self, data: Dict) -> Dict:
|
def handle_get_game_sale_api_request(self, data: Dict) -> Dict:
|
||||||
return { "type": data["type"], "length": 0, "gameSaleList": [] }
|
return {"type": data["type"], "length": 0, "gameSaleList": []}
|
||||||
|
|
||||||
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
|
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
|
||||||
reboot_start = datetime.strftime(datetime.now() - timedelta(hours=4), self.date_time_format)
|
reboot_start = datetime.strftime(
|
||||||
reboot_end = datetime.strftime(datetime.now() - timedelta(hours=3), self.date_time_format)
|
datetime.now() - timedelta(hours=4), self.date_time_format
|
||||||
|
)
|
||||||
|
reboot_end = datetime.strftime(
|
||||||
|
datetime.now() - timedelta(hours=3), self.date_time_format
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
"gameSetting": {
|
"gameSetting": {
|
||||||
"dataVersion": "1.00.00",
|
"dataVersion": "1.00.00",
|
||||||
@@ -102,7 +103,9 @@ class ChuniBase():
|
|||||||
}
|
}
|
||||||
|
|
||||||
def handle_get_user_activity_api_request(self, data: Dict) -> Dict:
|
def handle_get_user_activity_api_request(self, data: Dict) -> Dict:
|
||||||
user_activity_list = self.data.profile.get_profile_activity(data["userId"], data["kind"])
|
user_activity_list = self.data.profile.get_profile_activity(
|
||||||
|
data["userId"], data["kind"]
|
||||||
|
)
|
||||||
|
|
||||||
activity_list = []
|
activity_list = []
|
||||||
|
|
||||||
@@ -117,12 +120,13 @@ class ChuniBase():
|
|||||||
"userId": data["userId"],
|
"userId": data["userId"],
|
||||||
"length": len(activity_list),
|
"length": len(activity_list),
|
||||||
"kind": data["kind"],
|
"kind": data["kind"],
|
||||||
"userActivityList": activity_list
|
"userActivityList": activity_list,
|
||||||
}
|
}
|
||||||
|
|
||||||
def handle_get_user_character_api_request(self, data: Dict) -> Dict:
|
def handle_get_user_character_api_request(self, data: Dict) -> Dict:
|
||||||
characters = self.data.item.get_characters(data["userId"])
|
characters = self.data.item.get_characters(data["userId"])
|
||||||
if characters is None: return {}
|
if characters is None:
|
||||||
|
return {}
|
||||||
next_idx = -1
|
next_idx = -1
|
||||||
|
|
||||||
characterList = []
|
characterList = []
|
||||||
@@ -135,14 +139,16 @@ class ChuniBase():
|
|||||||
if len(characterList) >= int(data["maxCount"]):
|
if len(characterList) >= int(data["maxCount"]):
|
||||||
break
|
break
|
||||||
|
|
||||||
if len(characterList) >= int(data["maxCount"]) and len(characters) > int(data["maxCount"]) + int(data["nextIndex"]):
|
if len(characterList) >= int(data["maxCount"]) and len(characters) > int(
|
||||||
|
data["maxCount"]
|
||||||
|
) + int(data["nextIndex"]):
|
||||||
next_idx = int(data["maxCount"]) + int(data["nextIndex"]) + 1
|
next_idx = int(data["maxCount"]) + int(data["nextIndex"]) + 1
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"userId": data["userId"],
|
"userId": data["userId"],
|
||||||
"length": len(characterList),
|
"length": len(characterList),
|
||||||
"nextIndex": next_idx,
|
"nextIndex": next_idx,
|
||||||
"userCharacterList": characterList
|
"userCharacterList": characterList,
|
||||||
}
|
}
|
||||||
|
|
||||||
def handle_get_user_charge_api_request(self, data: Dict) -> Dict:
|
def handle_get_user_charge_api_request(self, data: Dict) -> Dict:
|
||||||
@@ -158,7 +164,7 @@ class ChuniBase():
|
|||||||
return {
|
return {
|
||||||
"userId": data["userId"],
|
"userId": data["userId"],
|
||||||
"length": len(charge_list),
|
"length": len(charge_list),
|
||||||
"userChargeList": charge_list
|
"userChargeList": charge_list,
|
||||||
}
|
}
|
||||||
|
|
||||||
def handle_get_user_course_api_request(self, data: Dict) -> Dict:
|
def handle_get_user_course_api_request(self, data: Dict) -> Dict:
|
||||||
@@ -168,7 +174,7 @@ class ChuniBase():
|
|||||||
"userId": data["userId"],
|
"userId": data["userId"],
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"nextIndex": -1,
|
"nextIndex": -1,
|
||||||
"userCourseList": []
|
"userCourseList": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
course_list = []
|
course_list = []
|
||||||
@@ -193,40 +199,37 @@ class ChuniBase():
|
|||||||
"userId": data["userId"],
|
"userId": data["userId"],
|
||||||
"length": len(course_list),
|
"length": len(course_list),
|
||||||
"nextIndex": next_idx,
|
"nextIndex": next_idx,
|
||||||
"userCourseList": course_list
|
"userCourseList": course_list,
|
||||||
}
|
}
|
||||||
|
|
||||||
def handle_get_user_data_api_request(self, data: Dict) -> Dict:
|
def handle_get_user_data_api_request(self, data: Dict) -> Dict:
|
||||||
p = self.data.profile.get_profile_data(data["userId"], self.version)
|
p = self.data.profile.get_profile_data(data["userId"], self.version)
|
||||||
if p is None: return {}
|
if p is None:
|
||||||
|
return {}
|
||||||
|
|
||||||
profile = p._asdict()
|
profile = p._asdict()
|
||||||
profile.pop("id")
|
profile.pop("id")
|
||||||
profile.pop("user")
|
profile.pop("user")
|
||||||
profile.pop("version")
|
profile.pop("version")
|
||||||
|
|
||||||
return {
|
return {"userId": data["userId"], "userData": profile}
|
||||||
"userId": data["userId"],
|
|
||||||
"userData": profile
|
|
||||||
}
|
|
||||||
|
|
||||||
def handle_get_user_data_ex_api_request(self, data: Dict) -> Dict:
|
def handle_get_user_data_ex_api_request(self, data: Dict) -> Dict:
|
||||||
p = self.data.profile.get_profile_data_ex(data["userId"], self.version)
|
p = self.data.profile.get_profile_data_ex(data["userId"], self.version)
|
||||||
if p is None: return {}
|
if p is None:
|
||||||
|
return {}
|
||||||
|
|
||||||
profile = p._asdict()
|
profile = p._asdict()
|
||||||
profile.pop("id")
|
profile.pop("id")
|
||||||
profile.pop("user")
|
profile.pop("user")
|
||||||
profile.pop("version")
|
profile.pop("version")
|
||||||
|
|
||||||
return {
|
return {"userId": data["userId"], "userDataEx": profile}
|
||||||
"userId": data["userId"],
|
|
||||||
"userDataEx": profile
|
|
||||||
}
|
|
||||||
|
|
||||||
def handle_get_user_duel_api_request(self, data: Dict) -> Dict:
|
def handle_get_user_duel_api_request(self, data: Dict) -> Dict:
|
||||||
user_duel_list = self.data.item.get_duels(data["userId"])
|
user_duel_list = self.data.item.get_duels(data["userId"])
|
||||||
if user_duel_list is None: return {}
|
if user_duel_list is None:
|
||||||
|
return {}
|
||||||
|
|
||||||
duel_list = []
|
duel_list = []
|
||||||
for duel in user_duel_list:
|
for duel in user_duel_list:
|
||||||
@@ -238,7 +241,7 @@ class ChuniBase():
|
|||||||
return {
|
return {
|
||||||
"userId": data["userId"],
|
"userId": data["userId"],
|
||||||
"length": len(duel_list),
|
"length": len(duel_list),
|
||||||
"userDuelList": duel_list
|
"userDuelList": duel_list,
|
||||||
}
|
}
|
||||||
|
|
||||||
def handle_get_user_favorite_item_api_request(self, data: Dict) -> Dict:
|
def handle_get_user_favorite_item_api_request(self, data: Dict) -> Dict:
|
||||||
@@ -247,7 +250,7 @@ class ChuniBase():
|
|||||||
"length": 0,
|
"length": 0,
|
||||||
"kind": data["kind"],
|
"kind": data["kind"],
|
||||||
"nextIndex": -1,
|
"nextIndex": -1,
|
||||||
"userFavoriteItemList": []
|
"userFavoriteItemList": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
def handle_get_user_favorite_music_api_request(self, data: Dict) -> Dict:
|
def handle_get_user_favorite_music_api_request(self, data: Dict) -> Dict:
|
||||||
@@ -255,11 +258,7 @@ class ChuniBase():
|
|||||||
This is handled via the webui, which we don't have right now
|
This is handled via the webui, which we don't have right now
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return {
|
return {"userId": data["userId"], "length": 0, "userFavoriteMusicList": []}
|
||||||
"userId": data["userId"],
|
|
||||||
"length": 0,
|
|
||||||
"userFavoriteMusicList": []
|
|
||||||
}
|
|
||||||
|
|
||||||
def handle_get_user_item_api_request(self, data: Dict) -> Dict:
|
def handle_get_user_item_api_request(self, data: Dict) -> Dict:
|
||||||
kind = int(int(data["nextIndex"]) / 10000000000)
|
kind = int(int(data["nextIndex"]) / 10000000000)
|
||||||
@@ -267,7 +266,12 @@ class ChuniBase():
|
|||||||
user_item_list = self.data.item.get_items(data["userId"], kind)
|
user_item_list = self.data.item.get_items(data["userId"], kind)
|
||||||
|
|
||||||
if user_item_list is None or len(user_item_list) == 0:
|
if user_item_list is None or len(user_item_list) == 0:
|
||||||
return {"userId": data["userId"], "nextIndex": -1, "itemKind": kind, "userItemList": []}
|
return {
|
||||||
|
"userId": data["userId"],
|
||||||
|
"nextIndex": -1,
|
||||||
|
"itemKind": kind,
|
||||||
|
"userItemList": [],
|
||||||
|
}
|
||||||
|
|
||||||
items: list[Dict[str, Any]] = []
|
items: list[Dict[str, Any]] = []
|
||||||
for i in range(next_idx, len(user_item_list)):
|
for i in range(next_idx, len(user_item_list)):
|
||||||
@@ -280,10 +284,18 @@ class ChuniBase():
|
|||||||
|
|
||||||
xout = kind * 10000000000 + next_idx + len(items)
|
xout = kind * 10000000000 + next_idx + len(items)
|
||||||
|
|
||||||
if len(items) < int(data["maxCount"]): nextIndex = 0
|
if len(items) < int(data["maxCount"]):
|
||||||
else: nextIndex = xout
|
nextIndex = 0
|
||||||
|
else:
|
||||||
|
nextIndex = xout
|
||||||
|
|
||||||
return {"userId": data["userId"], "nextIndex": nextIndex, "itemKind": kind, "length": len(items), "userItemList": items}
|
return {
|
||||||
|
"userId": data["userId"],
|
||||||
|
"nextIndex": nextIndex,
|
||||||
|
"itemKind": kind,
|
||||||
|
"length": len(items),
|
||||||
|
"userItemList": items,
|
||||||
|
}
|
||||||
|
|
||||||
def handle_get_user_login_bonus_api_request(self, data: Dict) -> Dict:
|
def handle_get_user_login_bonus_api_request(self, data: Dict) -> Dict:
|
||||||
"""
|
"""
|
||||||
@@ -294,23 +306,24 @@ class ChuniBase():
|
|||||||
"length": 2,
|
"length": 2,
|
||||||
"userLoginBonusList": [
|
"userLoginBonusList": [
|
||||||
{
|
{
|
||||||
"presetId": '10',
|
"presetId": "10",
|
||||||
"bonusCount": '0',
|
"bonusCount": "0",
|
||||||
"lastUpdateDate": "1970-01-01 09:00:00",
|
"lastUpdateDate": "1970-01-01 09:00:00",
|
||||||
"isWatched": "true"
|
"isWatched": "true",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"presetId": '20',
|
"presetId": "20",
|
||||||
"bonusCount": '0',
|
"bonusCount": "0",
|
||||||
"lastUpdateDate": "1970-01-01 09:00:00",
|
"lastUpdateDate": "1970-01-01 09:00:00",
|
||||||
"isWatched": "true"
|
"isWatched": "true",
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
def handle_get_user_map_api_request(self, data: Dict) -> Dict:
|
def handle_get_user_map_api_request(self, data: Dict) -> Dict:
|
||||||
user_map_list = self.data.item.get_maps(data["userId"])
|
user_map_list = self.data.item.get_maps(data["userId"])
|
||||||
if user_map_list is None: return {}
|
if user_map_list is None:
|
||||||
|
return {}
|
||||||
|
|
||||||
map_list = []
|
map_list = []
|
||||||
for map in user_map_list:
|
for map in user_map_list:
|
||||||
@@ -322,7 +335,7 @@ class ChuniBase():
|
|||||||
return {
|
return {
|
||||||
"userId": data["userId"],
|
"userId": data["userId"],
|
||||||
"length": len(map_list),
|
"length": len(map_list),
|
||||||
"userMapList": map_list
|
"userMapList": map_list,
|
||||||
}
|
}
|
||||||
|
|
||||||
def handle_get_user_music_api_request(self, data: Dict) -> Dict:
|
def handle_get_user_music_api_request(self, data: Dict) -> Dict:
|
||||||
@@ -332,7 +345,7 @@ class ChuniBase():
|
|||||||
"userId": data["userId"],
|
"userId": data["userId"],
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"nextIndex": -1,
|
"nextIndex": -1,
|
||||||
"userMusicList": [] #240
|
"userMusicList": [], # 240
|
||||||
}
|
}
|
||||||
song_list = []
|
song_list = []
|
||||||
next_idx = int(data["nextIndex"])
|
next_idx = int(data["nextIndex"])
|
||||||
@@ -351,10 +364,7 @@ class ChuniBase():
|
|||||||
song["length"] = len(song["userMusicDetailList"])
|
song["length"] = len(song["userMusicDetailList"])
|
||||||
|
|
||||||
if not found:
|
if not found:
|
||||||
song_list.append({
|
song_list.append({"length": 1, "userMusicDetailList": [tmp]})
|
||||||
"length": 1,
|
|
||||||
"userMusicDetailList": [tmp]
|
|
||||||
})
|
|
||||||
|
|
||||||
if len(song_list) >= max_ct:
|
if len(song_list) >= max_ct:
|
||||||
break
|
break
|
||||||
@@ -368,7 +378,7 @@ class ChuniBase():
|
|||||||
"userId": data["userId"],
|
"userId": data["userId"],
|
||||||
"length": len(song_list),
|
"length": len(song_list),
|
||||||
"nextIndex": next_idx,
|
"nextIndex": next_idx,
|
||||||
"userMusicList": song_list #240
|
"userMusicList": song_list, # 240
|
||||||
}
|
}
|
||||||
|
|
||||||
def handle_get_user_option_api_request(self, data: Dict) -> Dict:
|
def handle_get_user_option_api_request(self, data: Dict) -> Dict:
|
||||||
@@ -378,10 +388,7 @@ class ChuniBase():
|
|||||||
option.pop("id")
|
option.pop("id")
|
||||||
option.pop("user")
|
option.pop("user")
|
||||||
|
|
||||||
return {
|
return {"userId": data["userId"], "userGameOption": option}
|
||||||
"userId": data["userId"],
|
|
||||||
"userGameOption": option
|
|
||||||
}
|
|
||||||
|
|
||||||
def handle_get_user_option_ex_api_request(self, data: Dict) -> Dict:
|
def handle_get_user_option_ex_api_request(self, data: Dict) -> Dict:
|
||||||
p = self.data.profile.get_profile_option_ex(data["userId"])
|
p = self.data.profile.get_profile_option_ex(data["userId"])
|
||||||
@@ -390,18 +397,18 @@ class ChuniBase():
|
|||||||
option.pop("id")
|
option.pop("id")
|
||||||
option.pop("user")
|
option.pop("user")
|
||||||
|
|
||||||
return {
|
return {"userId": data["userId"], "userGameOptionEx": option}
|
||||||
"userId": data["userId"],
|
|
||||||
"userGameOptionEx": option
|
|
||||||
}
|
|
||||||
|
|
||||||
def read_wtf8(self, src):
|
def read_wtf8(self, src):
|
||||||
return bytes([ord(c) for c in src]).decode("utf-8")
|
return bytes([ord(c) for c in src]).decode("utf-8")
|
||||||
|
|
||||||
def handle_get_user_preview_api_request(self, data: Dict) -> Dict:
|
def handle_get_user_preview_api_request(self, data: Dict) -> Dict:
|
||||||
profile = self.data.profile.get_profile_preview(data["userId"], self.version)
|
profile = self.data.profile.get_profile_preview(data["userId"], self.version)
|
||||||
if profile is None: return None
|
if profile is None:
|
||||||
profile_character = self.data.item.get_character(data["userId"], profile["characterId"])
|
return None
|
||||||
|
profile_character = self.data.item.get_character(
|
||||||
|
data["userId"], profile["characterId"]
|
||||||
|
)
|
||||||
|
|
||||||
if profile_character is None:
|
if profile_character is None:
|
||||||
chara = {}
|
chara = {}
|
||||||
@@ -462,10 +469,7 @@ class ChuniBase():
|
|||||||
|
|
||||||
def handle_get_user_team_api_request(self, data: Dict) -> Dict:
|
def handle_get_user_team_api_request(self, data: Dict) -> Dict:
|
||||||
# TODO: Team
|
# TODO: Team
|
||||||
return {
|
return {"userId": data["userId"], "teamId": 0}
|
||||||
"userId": data["userId"],
|
|
||||||
"teamId": 0
|
|
||||||
}
|
|
||||||
|
|
||||||
def handle_get_team_course_setting_api_request(self, data: Dict) -> Dict:
|
def handle_get_team_course_setting_api_request(self, data: Dict) -> Dict:
|
||||||
return {
|
return {
|
||||||
@@ -489,18 +493,29 @@ class ChuniBase():
|
|||||||
|
|
||||||
if "userData" in upsert:
|
if "userData" in upsert:
|
||||||
try:
|
try:
|
||||||
upsert["userData"][0]["userName"] = self.read_wtf8(upsert["userData"][0]["userName"])
|
upsert["userData"][0]["userName"] = self.read_wtf8(
|
||||||
except: pass
|
upsert["userData"][0]["userName"]
|
||||||
|
)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
self.data.profile.put_profile_data(user_id, self.version, upsert["userData"][0])
|
self.data.profile.put_profile_data(
|
||||||
|
user_id, self.version, upsert["userData"][0]
|
||||||
|
)
|
||||||
if "userDataEx" in upsert:
|
if "userDataEx" in upsert:
|
||||||
self.data.profile.put_profile_data_ex(user_id, self.version, upsert["userDataEx"][0])
|
self.data.profile.put_profile_data_ex(
|
||||||
|
user_id, self.version, upsert["userDataEx"][0]
|
||||||
|
)
|
||||||
if "userGameOption" in upsert:
|
if "userGameOption" in upsert:
|
||||||
self.data.profile.put_profile_option(user_id, upsert["userGameOption"][0])
|
self.data.profile.put_profile_option(user_id, upsert["userGameOption"][0])
|
||||||
if "userGameOptionEx" in upsert:
|
if "userGameOptionEx" in upsert:
|
||||||
self.data.profile.put_profile_option_ex(user_id, upsert["userGameOptionEx"][0])
|
self.data.profile.put_profile_option_ex(
|
||||||
|
user_id, upsert["userGameOptionEx"][0]
|
||||||
|
)
|
||||||
if "userRecentRatingList" in upsert:
|
if "userRecentRatingList" in upsert:
|
||||||
self.data.profile.put_profile_recent_rating(user_id, upsert["userRecentRatingList"])
|
self.data.profile.put_profile_recent_rating(
|
||||||
|
user_id, upsert["userRecentRatingList"]
|
||||||
|
)
|
||||||
|
|
||||||
if "userCharacterList" in upsert:
|
if "userCharacterList" in upsert:
|
||||||
for character in upsert["userCharacterList"]:
|
for character in upsert["userCharacterList"]:
|
||||||
@@ -554,22 +569,22 @@ class ChuniBase():
|
|||||||
for emoney in upsert["userEmoneyList"]:
|
for emoney in upsert["userEmoneyList"]:
|
||||||
self.data.profile.put_profile_emoney(user_id, emoney)
|
self.data.profile.put_profile_emoney(user_id, emoney)
|
||||||
|
|
||||||
return { "returnCode": "1" }
|
return {"returnCode": "1"}
|
||||||
|
|
||||||
def handle_upsert_user_chargelog_api_request(self, data: Dict) -> Dict:
|
def handle_upsert_user_chargelog_api_request(self, data: Dict) -> Dict:
|
||||||
return { "returnCode": "1" }
|
return {"returnCode": "1"}
|
||||||
|
|
||||||
def handle_upsert_client_bookkeeping_api_request(self, data: Dict) -> Dict:
|
def handle_upsert_client_bookkeeping_api_request(self, data: Dict) -> Dict:
|
||||||
return { "returnCode": "1" }
|
return {"returnCode": "1"}
|
||||||
|
|
||||||
def handle_upsert_client_develop_api_request(self, data: Dict) -> Dict:
|
def handle_upsert_client_develop_api_request(self, data: Dict) -> Dict:
|
||||||
return { "returnCode": "1" }
|
return {"returnCode": "1"}
|
||||||
|
|
||||||
def handle_upsert_client_error_api_request(self, data: Dict) -> Dict:
|
def handle_upsert_client_error_api_request(self, data: Dict) -> Dict:
|
||||||
return { "returnCode": "1" }
|
return {"returnCode": "1"}
|
||||||
|
|
||||||
def handle_upsert_client_setting_api_request(self, data: Dict) -> Dict:
|
def handle_upsert_client_setting_api_request(self, data: Dict) -> Dict:
|
||||||
return { "returnCode": "1" }
|
return {"returnCode": "1"}
|
||||||
|
|
||||||
def handle_upsert_client_testmode_api_request(self, data: Dict) -> Dict:
|
def handle_upsert_client_testmode_api_request(self, data: Dict) -> Dict:
|
||||||
return { "returnCode": "1" }
|
return {"returnCode": "1"}
|
||||||
|
|||||||
+19
-6
@@ -1,19 +1,27 @@
|
|||||||
from core.config import CoreConfig
|
from core.config import CoreConfig
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
class ChuniServerConfig():
|
|
||||||
|
class ChuniServerConfig:
|
||||||
def __init__(self, parent_config: "ChuniConfig") -> None:
|
def __init__(self, parent_config: "ChuniConfig") -> None:
|
||||||
self.__config = parent_config
|
self.__config = parent_config
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def enable(self) -> bool:
|
def enable(self) -> bool:
|
||||||
return CoreConfig.get_config_field(self.__config, 'chuni', 'server', 'enable', default=True)
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "chuni", "server", "enable", default=True
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def loglevel(self) -> int:
|
def loglevel(self) -> int:
|
||||||
return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'chuni', 'server', 'loglevel', default="info"))
|
return CoreConfig.str_to_loglevel(
|
||||||
|
CoreConfig.get_config_field(
|
||||||
|
self.__config, "chuni", "server", "loglevel", default="info"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
class ChuniCryptoConfig():
|
|
||||||
|
class ChuniCryptoConfig:
|
||||||
def __init__(self, parent_config: "ChuniConfig") -> None:
|
def __init__(self, parent_config: "ChuniConfig") -> None:
|
||||||
self.__config = parent_config
|
self.__config = parent_config
|
||||||
|
|
||||||
@@ -24,11 +32,16 @@ class ChuniCryptoConfig():
|
|||||||
internal_version: [key, iv]
|
internal_version: [key, iv]
|
||||||
all values are hex strings
|
all values are hex strings
|
||||||
"""
|
"""
|
||||||
return CoreConfig.get_config_field(self.__config, 'chuni', 'crypto', 'keys', default={})
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "chuni", "crypto", "keys", default={}
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def encrypted_only(self) -> bool:
|
def encrypted_only(self) -> bool:
|
||||||
return CoreConfig.get_config_field(self.__config, 'chuni', 'crypto', 'encrypted_only', default=False)
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "chuni", "crypto", "encrypted_only", default=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ChuniConfig(dict):
|
class ChuniConfig(dict):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
|
|||||||
+16
-3
@@ -1,4 +1,4 @@
|
|||||||
class ChuniConstants():
|
class ChuniConstants:
|
||||||
GAME_CODE = "SDBT"
|
GAME_CODE = "SDBT"
|
||||||
GAME_CODE_NEW = "SDHD"
|
GAME_CODE_NEW = "SDHD"
|
||||||
|
|
||||||
@@ -18,8 +18,21 @@ class ChuniConstants():
|
|||||||
VER_CHUNITHM_NEW = 11
|
VER_CHUNITHM_NEW = 11
|
||||||
VER_CHUNITHM_NEW_PLUS = 12
|
VER_CHUNITHM_NEW_PLUS = 12
|
||||||
|
|
||||||
VERSION_NAMES = ["Chunithm", "Chunithm+", "Chunithm Air", "Chunithm Air+", "Chunithm Star", "Chunithm Star+", "Chunithm Amazon",
|
VERSION_NAMES = [
|
||||||
"Chunithm Amazon+", "Chunithm Crystal", "Chunithm Crystal+", "Chunithm Paradise", "Chunithm New!!", "Chunithm New!!+"]
|
"Chunithm",
|
||||||
|
"Chunithm+",
|
||||||
|
"Chunithm Air",
|
||||||
|
"Chunithm Air+",
|
||||||
|
"Chunithm Star",
|
||||||
|
"Chunithm Star+",
|
||||||
|
"Chunithm Amazon",
|
||||||
|
"Chunithm Amazon+",
|
||||||
|
"Chunithm Crystal",
|
||||||
|
"Chunithm Crystal+",
|
||||||
|
"Chunithm Paradise",
|
||||||
|
"Chunithm New!!",
|
||||||
|
"Chunithm New!!+",
|
||||||
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def game_ver_to_string(cls, ver: int):
|
def game_ver_to_string(cls, ver: int):
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from titles.chuni.base import ChuniBase
|
|||||||
from titles.chuni.const import ChuniConstants
|
from titles.chuni.const import ChuniConstants
|
||||||
from titles.chuni.config import ChuniConfig
|
from titles.chuni.config import ChuniConfig
|
||||||
|
|
||||||
|
|
||||||
class ChuniCrystal(ChuniBase):
|
class ChuniCrystal(ChuniBase):
|
||||||
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
|
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
|
||||||
super().__init__(core_cfg, game_cfg)
|
super().__init__(core_cfg, game_cfg)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from titles.chuni.base import ChuniBase
|
|||||||
from titles.chuni.const import ChuniConstants
|
from titles.chuni.const import ChuniConstants
|
||||||
from titles.chuni.config import ChuniConfig
|
from titles.chuni.config import ChuniConfig
|
||||||
|
|
||||||
|
|
||||||
class ChuniCrystalPlus(ChuniBase):
|
class ChuniCrystalPlus(ChuniBase):
|
||||||
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
|
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
|
||||||
super().__init__(core_cfg, game_cfg)
|
super().__init__(core_cfg, game_cfg)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from core.data import Data
|
|||||||
from core.config import CoreConfig
|
from core.config import CoreConfig
|
||||||
from titles.chuni.schema import *
|
from titles.chuni.schema import *
|
||||||
|
|
||||||
|
|
||||||
class ChuniData(Data):
|
class ChuniData(Data):
|
||||||
def __init__(self, cfg: CoreConfig) -> None:
|
def __init__(self, cfg: CoreConfig) -> None:
|
||||||
super().__init__(cfg)
|
super().__init__(cfg)
|
||||||
|
|||||||
+51
-22
@@ -28,12 +28,15 @@ from titles.chuni.paradise import ChuniParadise
|
|||||||
from titles.chuni.new import ChuniNew
|
from titles.chuni.new import ChuniNew
|
||||||
from titles.chuni.newplus import ChuniNewPlus
|
from titles.chuni.newplus import ChuniNewPlus
|
||||||
|
|
||||||
class ChuniServlet():
|
|
||||||
|
class ChuniServlet:
|
||||||
def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None:
|
def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None:
|
||||||
self.core_cfg = core_cfg
|
self.core_cfg = core_cfg
|
||||||
self.game_cfg = ChuniConfig()
|
self.game_cfg = ChuniConfig()
|
||||||
if path.exists(f"{cfg_dir}/{ChuniConstants.CONFIG_NAME}"):
|
if path.exists(f"{cfg_dir}/{ChuniConstants.CONFIG_NAME}"):
|
||||||
self.game_cfg.update(yaml.safe_load(open(f"{cfg_dir}/{ChuniConstants.CONFIG_NAME}")))
|
self.game_cfg.update(
|
||||||
|
yaml.safe_load(open(f"{cfg_dir}/{ChuniConstants.CONFIG_NAME}"))
|
||||||
|
)
|
||||||
|
|
||||||
self.versions = [
|
self.versions = [
|
||||||
ChuniBase(core_cfg, self.game_cfg),
|
ChuniBase(core_cfg, self.game_cfg),
|
||||||
@@ -56,8 +59,12 @@ class ChuniServlet():
|
|||||||
if not hasattr(self.logger, "inited"):
|
if not hasattr(self.logger, "inited"):
|
||||||
log_fmt_str = "[%(asctime)s] Chunithm | %(levelname)s | %(message)s"
|
log_fmt_str = "[%(asctime)s] Chunithm | %(levelname)s | %(message)s"
|
||||||
log_fmt = logging.Formatter(log_fmt_str)
|
log_fmt = logging.Formatter(log_fmt_str)
|
||||||
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.core_cfg.server.log_dir, "chuni"), encoding='utf8',
|
fileHandler = TimedRotatingFileHandler(
|
||||||
when="d", backupCount=10)
|
"{0}/{1}.log".format(self.core_cfg.server.log_dir, "chuni"),
|
||||||
|
encoding="utf8",
|
||||||
|
when="d",
|
||||||
|
backupCount=10,
|
||||||
|
)
|
||||||
|
|
||||||
fileHandler.setFormatter(log_fmt)
|
fileHandler.setFormatter(log_fmt)
|
||||||
|
|
||||||
@@ -68,24 +75,37 @@ class ChuniServlet():
|
|||||||
self.logger.addHandler(consoleHandler)
|
self.logger.addHandler(consoleHandler)
|
||||||
|
|
||||||
self.logger.setLevel(self.game_cfg.server.loglevel)
|
self.logger.setLevel(self.game_cfg.server.loglevel)
|
||||||
coloredlogs.install(level=self.game_cfg.server.loglevel, logger=self.logger, fmt=log_fmt_str)
|
coloredlogs.install(
|
||||||
|
level=self.game_cfg.server.loglevel, logger=self.logger, fmt=log_fmt_str
|
||||||
|
)
|
||||||
self.logger.inited = True
|
self.logger.inited = True
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_allnet_info(cls, game_code: str, core_cfg: CoreConfig, cfg_dir: str) -> Tuple[bool, str, str]:
|
def get_allnet_info(
|
||||||
|
cls, game_code: str, core_cfg: CoreConfig, cfg_dir: str
|
||||||
|
) -> Tuple[bool, str, str]:
|
||||||
game_cfg = ChuniConfig()
|
game_cfg = ChuniConfig()
|
||||||
if path.exists(f"{cfg_dir}/{ChuniConstants.CONFIG_NAME}"):
|
if path.exists(f"{cfg_dir}/{ChuniConstants.CONFIG_NAME}"):
|
||||||
game_cfg.update(yaml.safe_load(open(f"{cfg_dir}/{ChuniConstants.CONFIG_NAME}")))
|
game_cfg.update(
|
||||||
|
yaml.safe_load(open(f"{cfg_dir}/{ChuniConstants.CONFIG_NAME}"))
|
||||||
|
)
|
||||||
|
|
||||||
if not game_cfg.server.enable:
|
if not game_cfg.server.enable:
|
||||||
return (False, "", "")
|
return (False, "", "")
|
||||||
|
|
||||||
if core_cfg.server.is_develop:
|
if core_cfg.server.is_develop:
|
||||||
return (True, f"http://{core_cfg.title.hostname}:{core_cfg.title.port}/{game_code}/$v/", "")
|
return (
|
||||||
|
True,
|
||||||
|
f"http://{core_cfg.title.hostname}:{core_cfg.title.port}/{game_code}/$v/",
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
|
||||||
return (True, f"http://{core_cfg.title.hostname}/{game_code}/$v/", "")
|
return (True, f"http://{core_cfg.title.hostname}/{game_code}/$v/", "")
|
||||||
|
|
||||||
def render_POST(self, request: Request, version: int, url_path: str) -> bytes:
|
def render_POST(self, request: Request, version: int, url_path: str) -> bytes:
|
||||||
|
if url_path.lower() == "/ping":
|
||||||
|
return zlib.compress(b'{"returnCode": "1"}')
|
||||||
|
|
||||||
req_raw = request.content.getvalue()
|
req_raw = request.content.getvalue()
|
||||||
url_split = url_path.split("/")
|
url_split = url_path.split("/")
|
||||||
encrtped = False
|
encrtped = False
|
||||||
@@ -128,49 +148,58 @@ class ChuniServlet():
|
|||||||
crypt = AES.new(
|
crypt = AES.new(
|
||||||
bytes.fromhex(self.game_cfg.crypto.keys[str(internal_ver)][0]),
|
bytes.fromhex(self.game_cfg.crypto.keys[str(internal_ver)][0]),
|
||||||
AES.MODE_CBC,
|
AES.MODE_CBC,
|
||||||
bytes.fromhex(self.game_cfg.crypto.keys[str(internal_ver)][1])
|
bytes.fromhex(self.game_cfg.crypto.keys[str(internal_ver)][1]),
|
||||||
)
|
)
|
||||||
|
|
||||||
req_raw = crypt.decrypt(req_raw)
|
req_raw = crypt.decrypt(req_raw)
|
||||||
|
|
||||||
except:
|
except:
|
||||||
self.logger.error(f"Failed to decrypt v{version} request to {endpoint} -> {req_raw}")
|
self.logger.error(
|
||||||
return zlib.compress("{\"stat\": \"0\"}".encode("utf-8"))
|
f"Failed to decrypt v{version} request to {endpoint} -> {req_raw}"
|
||||||
|
)
|
||||||
|
return zlib.compress(b'{"stat": "0"}')
|
||||||
|
|
||||||
encrtped = True
|
encrtped = True
|
||||||
|
|
||||||
if not encrtped and self.game_cfg.crypto.encrypted_only:
|
if not encrtped and self.game_cfg.crypto.encrypted_only:
|
||||||
self.logger.error(f"Unencrypted v{version} {endpoint} request, but config is set to encrypted only: {req_raw}")
|
self.logger.error(
|
||||||
return zlib.compress("{\"stat\": \"0\"}".encode("utf-8"))
|
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)
|
unzip = zlib.decompress(req_raw)
|
||||||
|
|
||||||
except zlib.error as e:
|
except zlib.error as e:
|
||||||
self.logger.error(f"Failed to decompress v{version} {endpoint} request -> {e}")
|
self.logger.error(
|
||||||
|
f"Failed to decompress v{version} {endpoint} request -> {e}"
|
||||||
|
)
|
||||||
return b""
|
return b""
|
||||||
|
|
||||||
req_data = json.loads(unzip)
|
req_data = json.loads(unzip)
|
||||||
|
|
||||||
self.logger.info(f"v{version} {endpoint} request from {request.getClientAddress().host}")
|
self.logger.info(
|
||||||
|
f"v{version} {endpoint} request from {request.getClientAddress().host}"
|
||||||
|
)
|
||||||
self.logger.debug(req_data)
|
self.logger.debug(req_data)
|
||||||
|
|
||||||
func_to_find = "handle_" + inflection.underscore(endpoint) + "_request"
|
func_to_find = "handle_" + inflection.underscore(endpoint) + "_request"
|
||||||
|
|
||||||
|
if not hasattr(self.versions[internal_ver], func_to_find):
|
||||||
|
self.logger.warning(f"Unhandled v{version} request {endpoint}")
|
||||||
|
resp = {"returnCode": 1}
|
||||||
|
|
||||||
|
else:
|
||||||
try:
|
try:
|
||||||
handler = getattr(self.versions[internal_ver], func_to_find)
|
handler = getattr(self.versions[internal_ver], func_to_find)
|
||||||
resp = handler(req_data)
|
resp = handler(req_data)
|
||||||
|
|
||||||
except AttributeError as e:
|
|
||||||
self.logger.warning(f"Unhandled v{version} request {endpoint} - {e}")
|
|
||||||
return zlib.compress("{\"stat\": \"0\"}".encode("utf-8"))
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Error handling v{version} method {endpoint} - {e}")
|
self.logger.error(f"Error handling v{version} method {endpoint} - {e}")
|
||||||
return zlib.compress("{\"stat\": \"0\"}".encode("utf-8"))
|
return zlib.compress(b'{"stat": "0"}')
|
||||||
|
|
||||||
if resp == None:
|
if resp == None:
|
||||||
resp = {'returnCode': 1}
|
resp = {"returnCode": 1}
|
||||||
|
|
||||||
self.logger.debug(f"Response {resp}")
|
self.logger.debug(f"Response {resp}")
|
||||||
|
|
||||||
@@ -184,7 +213,7 @@ class ChuniServlet():
|
|||||||
crypt = AES.new(
|
crypt = AES.new(
|
||||||
bytes.fromhex(self.game_cfg.crypto.keys[str(internal_ver)][0]),
|
bytes.fromhex(self.game_cfg.crypto.keys[str(internal_ver)][0]),
|
||||||
AES.MODE_CBC,
|
AES.MODE_CBC,
|
||||||
bytes.fromhex(self.game_cfg.crypto.keys[str(internal_ver)][1])
|
bytes.fromhex(self.game_cfg.crypto.keys[str(internal_ver)][1]),
|
||||||
)
|
)
|
||||||
|
|
||||||
return crypt.encrypt(padded)
|
return crypt.encrypt(padded)
|
||||||
|
|||||||
+26
-22
@@ -9,13 +9,9 @@ from titles.chuni.database import ChuniData
|
|||||||
from titles.chuni.base import ChuniBase
|
from titles.chuni.base import ChuniBase
|
||||||
from titles.chuni.config import ChuniConfig
|
from titles.chuni.config import ChuniConfig
|
||||||
|
|
||||||
class ChuniNew(ChuniBase):
|
|
||||||
|
|
||||||
ITEM_TYPE = {
|
class ChuniNew(ChuniBase):
|
||||||
"character": 20,
|
ITEM_TYPE = {"character": 20, "story": 21, "card": 22}
|
||||||
"story": 21,
|
|
||||||
"card": 22
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
|
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
|
||||||
self.core_cfg = core_cfg
|
self.core_cfg = core_cfg
|
||||||
@@ -27,10 +23,18 @@ class ChuniNew(ChuniBase):
|
|||||||
self.version = ChuniConstants.VER_CHUNITHM_NEW
|
self.version = ChuniConstants.VER_CHUNITHM_NEW
|
||||||
|
|
||||||
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
|
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
|
||||||
match_start = datetime.strftime(datetime.now() - timedelta(hours=10), self.date_time_format)
|
match_start = datetime.strftime(
|
||||||
match_end = datetime.strftime(datetime.now() + timedelta(hours=10), self.date_time_format)
|
datetime.now() - timedelta(hours=10), self.date_time_format
|
||||||
reboot_start = datetime.strftime(datetime.now() - timedelta(hours=11), self.date_time_format)
|
)
|
||||||
reboot_end = datetime.strftime(datetime.now() - timedelta(hours=10), self.date_time_format)
|
match_end = datetime.strftime(
|
||||||
|
datetime.now() + timedelta(hours=10), self.date_time_format
|
||||||
|
)
|
||||||
|
reboot_start = datetime.strftime(
|
||||||
|
datetime.now() - timedelta(hours=11), self.date_time_format
|
||||||
|
)
|
||||||
|
reboot_end = datetime.strftime(
|
||||||
|
datetime.now() - timedelta(hours=10), self.date_time_format
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
"gameSetting": {
|
"gameSetting": {
|
||||||
"isMaintenance": "false",
|
"isMaintenance": "false",
|
||||||
@@ -56,11 +60,14 @@ class ChuniNew(ChuniBase):
|
|||||||
"isAou": "false",
|
"isAou": "false",
|
||||||
}
|
}
|
||||||
|
|
||||||
def handle_delete_token_api_request(self, data: Dict) -> Dict:
|
def handle_remove_token_api_request(self, data: Dict) -> Dict:
|
||||||
return { "returnCode": "1" }
|
return { "returnCode": "1" }
|
||||||
|
|
||||||
|
def handle_delete_token_api_request(self, data: Dict) -> Dict:
|
||||||
|
return {"returnCode": "1"}
|
||||||
|
|
||||||
def handle_create_token_api_request(self, data: Dict) -> Dict:
|
def handle_create_token_api_request(self, data: Dict) -> Dict:
|
||||||
return { "returnCode": "1" }
|
return {"returnCode": "1"}
|
||||||
|
|
||||||
def handle_get_user_map_area_api_request(self, data: Dict) -> Dict:
|
def handle_get_user_map_area_api_request(self, data: Dict) -> Dict:
|
||||||
user_map_areas = self.data.item.get_map_areas(data["userId"])
|
user_map_areas = self.data.item.get_map_areas(data["userId"])
|
||||||
@@ -72,21 +79,18 @@ class ChuniNew(ChuniBase):
|
|||||||
tmp.pop("user")
|
tmp.pop("user")
|
||||||
map_areas.append(tmp)
|
map_areas.append(tmp)
|
||||||
|
|
||||||
return {
|
return {"userId": data["userId"], "userMapAreaList": map_areas}
|
||||||
"userId": data["userId"],
|
|
||||||
"userMapAreaList": map_areas
|
|
||||||
}
|
|
||||||
|
|
||||||
def handle_get_user_symbol_chat_setting_api_request(self, data: Dict) -> Dict:
|
def handle_get_user_symbol_chat_setting_api_request(self, data: Dict) -> Dict:
|
||||||
return {
|
return {"userId": data["userId"], "symbolCharInfoList": []}
|
||||||
"userId": data["userId"],
|
|
||||||
"symbolCharInfoList": []
|
|
||||||
}
|
|
||||||
|
|
||||||
def handle_get_user_preview_api_request(self, data: Dict) -> Dict:
|
def handle_get_user_preview_api_request(self, data: Dict) -> Dict:
|
||||||
profile = self.data.profile.get_profile_preview(data["userId"], self.version)
|
profile = self.data.profile.get_profile_preview(data["userId"], self.version)
|
||||||
if profile is None: return None
|
if profile is None:
|
||||||
profile_character = self.data.item.get_character(data["userId"], profile["characterId"])
|
return None
|
||||||
|
profile_character = self.data.item.get_character(
|
||||||
|
data["userId"], profile["characterId"]
|
||||||
|
)
|
||||||
|
|
||||||
if profile_character is None:
|
if profile_character is None:
|
||||||
chara = {}
|
chara = {}
|
||||||
|
|||||||
+13
-4
@@ -7,6 +7,7 @@ from titles.chuni.new import ChuniNew
|
|||||||
from titles.chuni.const import ChuniConstants
|
from titles.chuni.const import ChuniConstants
|
||||||
from titles.chuni.config import ChuniConfig
|
from titles.chuni.config import ChuniConfig
|
||||||
|
|
||||||
|
|
||||||
class ChuniNewPlus(ChuniNew):
|
class ChuniNewPlus(ChuniNew):
|
||||||
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
|
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
|
||||||
super().__init__(core_cfg, game_cfg)
|
super().__init__(core_cfg, game_cfg)
|
||||||
@@ -16,8 +17,16 @@ class ChuniNewPlus(ChuniNew):
|
|||||||
ret = super().handle_get_game_setting_api_request(data)
|
ret = super().handle_get_game_setting_api_request(data)
|
||||||
ret["gameSetting"]["romVersion"] = "2.05.00"
|
ret["gameSetting"]["romVersion"] = "2.05.00"
|
||||||
ret["gameSetting"]["dataVersion"] = "2.05.00"
|
ret["gameSetting"]["dataVersion"] = "2.05.00"
|
||||||
ret["gameSetting"]["matchingUri"] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/205/ChuniServlet/"
|
ret["gameSetting"][
|
||||||
ret["gameSetting"]["matchingUriX"] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/205/ChuniServlet/"
|
"matchingUri"
|
||||||
ret["gameSetting"]["udpHolePunchUri"] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/205/ChuniServlet/"
|
] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/205/ChuniServlet/"
|
||||||
ret["gameSetting"]["reflectorUri"] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/205/ChuniServlet/"
|
ret["gameSetting"][
|
||||||
|
"matchingUriX"
|
||||||
|
] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/205/ChuniServlet/"
|
||||||
|
ret["gameSetting"][
|
||||||
|
"udpHolePunchUri"
|
||||||
|
] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/205/ChuniServlet/"
|
||||||
|
ret["gameSetting"][
|
||||||
|
"reflectorUri"
|
||||||
|
] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/205/ChuniServlet/"
|
||||||
return ret
|
return ret
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from titles.chuni.base import ChuniBase
|
|||||||
from titles.chuni.const import ChuniConstants
|
from titles.chuni.const import ChuniConstants
|
||||||
from titles.chuni.config import ChuniConfig
|
from titles.chuni.config import ChuniConfig
|
||||||
|
|
||||||
|
|
||||||
class ChuniParadise(ChuniBase):
|
class ChuniParadise(ChuniBase):
|
||||||
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
|
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
|
||||||
super().__init__(core_cfg, game_cfg)
|
super().__init__(core_cfg, game_cfg)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from titles.chuni.base import ChuniBase
|
|||||||
from titles.chuni.const import ChuniConstants
|
from titles.chuni.const import ChuniConstants
|
||||||
from titles.chuni.config import ChuniConfig
|
from titles.chuni.config import ChuniConfig
|
||||||
|
|
||||||
|
|
||||||
class ChuniPlus(ChuniBase):
|
class ChuniPlus(ChuniBase):
|
||||||
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
|
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
|
||||||
super().__init__(core_cfg, game_cfg)
|
super().__init__(core_cfg, game_cfg)
|
||||||
|
|||||||
+83
-52
@@ -7,13 +7,23 @@ from core.config import CoreConfig
|
|||||||
from titles.chuni.database import ChuniData
|
from titles.chuni.database import ChuniData
|
||||||
from titles.chuni.const import ChuniConstants
|
from titles.chuni.const import ChuniConstants
|
||||||
|
|
||||||
|
|
||||||
class ChuniReader(BaseReader):
|
class ChuniReader(BaseReader):
|
||||||
def __init__(self, config: CoreConfig, version: int, bin_dir: Optional[str], opt_dir: Optional[str], extra: Optional[str]) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
config: CoreConfig,
|
||||||
|
version: int,
|
||||||
|
bin_dir: Optional[str],
|
||||||
|
opt_dir: Optional[str],
|
||||||
|
extra: Optional[str],
|
||||||
|
) -> None:
|
||||||
super().__init__(config, version, bin_dir, opt_dir, extra)
|
super().__init__(config, version, bin_dir, opt_dir, extra)
|
||||||
self.data = ChuniData(config)
|
self.data = ChuniData(config)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.logger.info(f"Start importer for {ChuniConstants.game_ver_to_string(version)}")
|
self.logger.info(
|
||||||
|
f"Start importer for {ChuniConstants.game_ver_to_string(version)}"
|
||||||
|
)
|
||||||
except IndexError:
|
except IndexError:
|
||||||
self.logger.error(f"Invalid chunithm version {version}")
|
self.logger.error(f"Invalid chunithm version {version}")
|
||||||
exit(1)
|
exit(1)
|
||||||
@@ -37,18 +47,20 @@ class ChuniReader(BaseReader):
|
|||||||
for root, dirs, files in walk(evt_dir):
|
for root, dirs, files in walk(evt_dir):
|
||||||
for dir in dirs:
|
for dir in dirs:
|
||||||
if path.exists(f"{root}/{dir}/Event.xml"):
|
if path.exists(f"{root}/{dir}/Event.xml"):
|
||||||
with open(f"{root}/{dir}/Event.xml", 'rb') as fp:
|
with open(f"{root}/{dir}/Event.xml", "rb") as fp:
|
||||||
bytedata = fp.read()
|
bytedata = fp.read()
|
||||||
strdata = bytedata.decode('UTF-8')
|
strdata = bytedata.decode("UTF-8")
|
||||||
|
|
||||||
xml_root = ET.fromstring(strdata)
|
xml_root = ET.fromstring(strdata)
|
||||||
for name in xml_root.findall('name'):
|
for name in xml_root.findall("name"):
|
||||||
id = name.find('id').text
|
id = name.find("id").text
|
||||||
name = name.find('str').text
|
name = name.find("str").text
|
||||||
for substances in xml_root.findall('substances'):
|
for substances in xml_root.findall("substances"):
|
||||||
event_type = substances.find('type').text
|
event_type = substances.find("type").text
|
||||||
|
|
||||||
result = self.data.static.put_event(self.version, id, event_type, name)
|
result = self.data.static.put_event(
|
||||||
|
self.version, id, event_type, name
|
||||||
|
)
|
||||||
if result is not None:
|
if result is not None:
|
||||||
self.logger.info(f"Inserted event {id}")
|
self.logger.info(f"Inserted event {id}")
|
||||||
else:
|
else:
|
||||||
@@ -58,37 +70,43 @@ class ChuniReader(BaseReader):
|
|||||||
for root, dirs, files in walk(music_dir):
|
for root, dirs, files in walk(music_dir):
|
||||||
for dir in dirs:
|
for dir in dirs:
|
||||||
if path.exists(f"{root}/{dir}/Music.xml"):
|
if path.exists(f"{root}/{dir}/Music.xml"):
|
||||||
with open(f"{root}/{dir}/Music.xml", 'rb') as fp:
|
with open(f"{root}/{dir}/Music.xml", "rb") as fp:
|
||||||
bytedata = fp.read()
|
bytedata = fp.read()
|
||||||
strdata = bytedata.decode('UTF-8')
|
strdata = bytedata.decode("UTF-8")
|
||||||
|
|
||||||
xml_root = ET.fromstring(strdata)
|
xml_root = ET.fromstring(strdata)
|
||||||
for name in xml_root.findall('name'):
|
for name in xml_root.findall("name"):
|
||||||
song_id = name.find('id').text
|
song_id = name.find("id").text
|
||||||
title = name.find('str').text
|
title = name.find("str").text
|
||||||
|
|
||||||
for artistName in xml_root.findall('artistName'):
|
for artistName in xml_root.findall("artistName"):
|
||||||
artist = artistName.find('str').text
|
artist = artistName.find("str").text
|
||||||
|
|
||||||
for genreNames in xml_root.findall('genreNames'):
|
for genreNames in xml_root.findall("genreNames"):
|
||||||
for list_ in genreNames.findall('list'):
|
for list_ in genreNames.findall("list"):
|
||||||
for StringID in list_.findall('StringID'):
|
for StringID in list_.findall("StringID"):
|
||||||
genre = StringID.find('str').text
|
genre = StringID.find("str").text
|
||||||
|
|
||||||
for jaketFile in xml_root.findall('jaketFile'): #nice typo, SEGA
|
for jaketFile in xml_root.findall("jaketFile"): # nice typo, SEGA
|
||||||
jacket_path = jaketFile.find('path').text
|
jacket_path = jaketFile.find("path").text
|
||||||
|
|
||||||
for fumens in xml_root.findall('fumens'):
|
for fumens in xml_root.findall("fumens"):
|
||||||
for MusicFumenData in fumens.findall('MusicFumenData'):
|
for MusicFumenData in fumens.findall("MusicFumenData"):
|
||||||
fumen_path = MusicFumenData.find('file').find("path")
|
fumen_path = MusicFumenData.find("file").find("path")
|
||||||
|
|
||||||
if fumen_path is not None:
|
if fumen_path is not None:
|
||||||
chart_id = MusicFumenData.find('type').find('id').text
|
chart_id = MusicFumenData.find("type").find("id").text
|
||||||
if chart_id == "4":
|
if chart_id == "4":
|
||||||
level = float(xml_root.find("starDifType").text)
|
level = float(xml_root.find("starDifType").text)
|
||||||
we_chara = xml_root.find("worldsEndTagName").find("str").text
|
we_chara = (
|
||||||
|
xml_root.find("worldsEndTagName")
|
||||||
|
.find("str")
|
||||||
|
.text
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
level = float(f"{MusicFumenData.find('level').text}.{MusicFumenData.find('levelDecimal').text}")
|
level = float(
|
||||||
|
f"{MusicFumenData.find('level').text}.{MusicFumenData.find('levelDecimal').text}"
|
||||||
|
)
|
||||||
we_chara = None
|
we_chara = None
|
||||||
|
|
||||||
result = self.data.static.put_music(
|
result = self.data.static.put_music(
|
||||||
@@ -100,31 +118,42 @@ class ChuniReader(BaseReader):
|
|||||||
level,
|
level,
|
||||||
genre,
|
genre,
|
||||||
jacket_path,
|
jacket_path,
|
||||||
we_chara
|
we_chara,
|
||||||
)
|
)
|
||||||
|
|
||||||
if result is not None:
|
if result is not None:
|
||||||
self.logger.info(f"Inserted music {song_id} chart {chart_id}")
|
self.logger.info(
|
||||||
|
f"Inserted music {song_id} chart {chart_id}"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.logger.warn(f"Failed to insert music {song_id} chart {chart_id}")
|
self.logger.warn(
|
||||||
|
f"Failed to insert music {song_id} chart {chart_id}"
|
||||||
|
)
|
||||||
|
|
||||||
def read_charges(self, charge_dir: str) -> None:
|
def read_charges(self, charge_dir: str) -> None:
|
||||||
for root, dirs, files in walk(charge_dir):
|
for root, dirs, files in walk(charge_dir):
|
||||||
for dir in dirs:
|
for dir in dirs:
|
||||||
if path.exists(f"{root}/{dir}/ChargeItem.xml"):
|
if path.exists(f"{root}/{dir}/ChargeItem.xml"):
|
||||||
with open(f"{root}/{dir}/ChargeItem.xml", 'rb') as fp:
|
with open(f"{root}/{dir}/ChargeItem.xml", "rb") as fp:
|
||||||
bytedata = fp.read()
|
bytedata = fp.read()
|
||||||
strdata = bytedata.decode('UTF-8')
|
strdata = bytedata.decode("UTF-8")
|
||||||
|
|
||||||
xml_root = ET.fromstring(strdata)
|
xml_root = ET.fromstring(strdata)
|
||||||
for name in xml_root.findall('name'):
|
for name in xml_root.findall("name"):
|
||||||
id = name.find('id').text
|
id = name.find("id").text
|
||||||
name = name.find('str').text
|
name = name.find("str").text
|
||||||
expirationDays = xml_root.find('expirationDays').text
|
expirationDays = xml_root.find("expirationDays").text
|
||||||
consumeType = xml_root.find('consumeType').text
|
consumeType = xml_root.find("consumeType").text
|
||||||
sellingAppeal = bool(xml_root.find('sellingAppeal').text)
|
sellingAppeal = bool(xml_root.find("sellingAppeal").text)
|
||||||
|
|
||||||
result = self.data.static.put_charge(self.version, id, name, expirationDays, consumeType, sellingAppeal)
|
result = self.data.static.put_charge(
|
||||||
|
self.version,
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
expirationDays,
|
||||||
|
consumeType,
|
||||||
|
sellingAppeal,
|
||||||
|
)
|
||||||
|
|
||||||
if result is not None:
|
if result is not None:
|
||||||
self.logger.info(f"Inserted charge {id}")
|
self.logger.info(f"Inserted charge {id}")
|
||||||
@@ -135,21 +164,23 @@ class ChuniReader(BaseReader):
|
|||||||
for root, dirs, files in walk(avatar_dir):
|
for root, dirs, files in walk(avatar_dir):
|
||||||
for dir in dirs:
|
for dir in dirs:
|
||||||
if path.exists(f"{root}/{dir}/AvatarAccessory.xml"):
|
if path.exists(f"{root}/{dir}/AvatarAccessory.xml"):
|
||||||
with open(f"{root}/{dir}/AvatarAccessory.xml", 'rb') as fp:
|
with open(f"{root}/{dir}/AvatarAccessory.xml", "rb") as fp:
|
||||||
bytedata = fp.read()
|
bytedata = fp.read()
|
||||||
strdata = bytedata.decode('UTF-8')
|
strdata = bytedata.decode("UTF-8")
|
||||||
|
|
||||||
xml_root = ET.fromstring(strdata)
|
xml_root = ET.fromstring(strdata)
|
||||||
for name in xml_root.findall('name'):
|
for name in xml_root.findall("name"):
|
||||||
id = name.find('id').text
|
id = name.find("id").text
|
||||||
name = name.find('str').text
|
name = name.find("str").text
|
||||||
category = xml_root.find('category').text
|
category = xml_root.find("category").text
|
||||||
for image in xml_root.findall('image'):
|
for image in xml_root.findall("image"):
|
||||||
iconPath = image.find('path').text
|
iconPath = image.find("path").text
|
||||||
for texture in xml_root.findall('texture'):
|
for texture in xml_root.findall("texture"):
|
||||||
texturePath = texture.find('path').text
|
texturePath = texture.find("path").text
|
||||||
|
|
||||||
result = self.data.static.put_avatar(self.version, id, name, category, iconPath, texturePath)
|
result = self.data.static.put_avatar(
|
||||||
|
self.version, id, name, category, iconPath, texturePath
|
||||||
|
)
|
||||||
|
|
||||||
if result is not None:
|
if result is not None:
|
||||||
self.logger.info(f"Inserted avatarAccessory {id}")
|
self.logger.info(f"Inserted avatarAccessory {id}")
|
||||||
|
|||||||
+59
-29
@@ -13,7 +13,11 @@ character = Table(
|
|||||||
"chuni_item_character",
|
"chuni_item_character",
|
||||||
metadata,
|
metadata,
|
||||||
Column("id", Integer, primary_key=True, nullable=False),
|
Column("id", Integer, primary_key=True, nullable=False),
|
||||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
Column(
|
||||||
|
"user",
|
||||||
|
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
Column("characterId", Integer),
|
Column("characterId", Integer),
|
||||||
Column("level", Integer),
|
Column("level", Integer),
|
||||||
Column("param1", Integer),
|
Column("param1", Integer),
|
||||||
@@ -26,27 +30,35 @@ character = Table(
|
|||||||
Column("assignIllust", Integer),
|
Column("assignIllust", Integer),
|
||||||
Column("exMaxLv", Integer),
|
Column("exMaxLv", Integer),
|
||||||
UniqueConstraint("user", "characterId", name="chuni_item_character_uk"),
|
UniqueConstraint("user", "characterId", name="chuni_item_character_uk"),
|
||||||
mysql_charset='utf8mb4'
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
item = Table(
|
item = Table(
|
||||||
"chuni_item_item",
|
"chuni_item_item",
|
||||||
metadata,
|
metadata,
|
||||||
Column("id", Integer, primary_key=True, nullable=False),
|
Column("id", Integer, primary_key=True, nullable=False),
|
||||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
Column(
|
||||||
|
"user",
|
||||||
|
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
Column("itemId", Integer),
|
Column("itemId", Integer),
|
||||||
Column("itemKind", Integer),
|
Column("itemKind", Integer),
|
||||||
Column("stock", Integer),
|
Column("stock", Integer),
|
||||||
Column("isValid", Boolean),
|
Column("isValid", Boolean),
|
||||||
UniqueConstraint("user", "itemId", "itemKind", name="chuni_item_item_uk"),
|
UniqueConstraint("user", "itemId", "itemKind", name="chuni_item_item_uk"),
|
||||||
mysql_charset='utf8mb4'
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
duel = Table(
|
duel = Table(
|
||||||
"chuni_item_duel",
|
"chuni_item_duel",
|
||||||
metadata,
|
metadata,
|
||||||
Column("id", Integer, primary_key=True, nullable=False),
|
Column("id", Integer, primary_key=True, nullable=False),
|
||||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
Column(
|
||||||
|
"user",
|
||||||
|
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
Column("duelId", Integer),
|
Column("duelId", Integer),
|
||||||
Column("progress", Integer),
|
Column("progress", Integer),
|
||||||
Column("point", Integer),
|
Column("point", Integer),
|
||||||
@@ -57,14 +69,18 @@ duel = Table(
|
|||||||
Column("param3", Integer),
|
Column("param3", Integer),
|
||||||
Column("param4", Integer),
|
Column("param4", Integer),
|
||||||
UniqueConstraint("user", "duelId", name="chuni_item_duel_uk"),
|
UniqueConstraint("user", "duelId", name="chuni_item_duel_uk"),
|
||||||
mysql_charset='utf8mb4'
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
map = Table(
|
map = Table(
|
||||||
"chuni_item_map",
|
"chuni_item_map",
|
||||||
metadata,
|
metadata,
|
||||||
Column("id", Integer, primary_key=True, nullable=False),
|
Column("id", Integer, primary_key=True, nullable=False),
|
||||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
Column(
|
||||||
|
"user",
|
||||||
|
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
Column("mapId", Integer),
|
Column("mapId", Integer),
|
||||||
Column("position", Integer),
|
Column("position", Integer),
|
||||||
Column("isClear", Boolean),
|
Column("isClear", Boolean),
|
||||||
@@ -75,14 +91,18 @@ map = Table(
|
|||||||
Column("statusCount", Integer),
|
Column("statusCount", Integer),
|
||||||
Column("isValid", Boolean),
|
Column("isValid", Boolean),
|
||||||
UniqueConstraint("user", "mapId", name="chuni_item_map_uk"),
|
UniqueConstraint("user", "mapId", name="chuni_item_map_uk"),
|
||||||
mysql_charset='utf8mb4'
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
map_area = Table(
|
map_area = Table(
|
||||||
"chuni_item_map_area",
|
"chuni_item_map_area",
|
||||||
metadata,
|
metadata,
|
||||||
Column("id", Integer, primary_key=True, nullable=False),
|
Column("id", Integer, primary_key=True, nullable=False),
|
||||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
Column(
|
||||||
|
"user",
|
||||||
|
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
Column("mapAreaId", Integer),
|
Column("mapAreaId", Integer),
|
||||||
Column("rate", Integer),
|
Column("rate", Integer),
|
||||||
Column("isClear", Boolean),
|
Column("isClear", Boolean),
|
||||||
@@ -91,9 +111,10 @@ map_area = Table(
|
|||||||
Column("statusCount", Integer),
|
Column("statusCount", Integer),
|
||||||
Column("remainGridCount", Integer),
|
Column("remainGridCount", Integer),
|
||||||
UniqueConstraint("user", "mapAreaId", name="chuni_item_map_area_uk"),
|
UniqueConstraint("user", "mapAreaId", name="chuni_item_map_area_uk"),
|
||||||
mysql_charset='utf8mb4'
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ChuniItemData(BaseData):
|
class ChuniItemData(BaseData):
|
||||||
def put_character(self, user_id: int, character_data: Dict) -> Optional[int]:
|
def put_character(self, user_id: int, character_data: Dict) -> Optional[int]:
|
||||||
character_data["user"] = user_id
|
character_data["user"] = user_id
|
||||||
@@ -104,24 +125,26 @@ class ChuniItemData(BaseData):
|
|||||||
conflict = sql.on_duplicate_key_update(**character_data)
|
conflict = sql.on_duplicate_key_update(**character_data)
|
||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
def get_character(self, user_id: int, character_id: int) -> Optional[Dict]:
|
def get_character(self, user_id: int, character_id: int) -> Optional[Dict]:
|
||||||
sql = select(character).where(and_(
|
sql = select(character).where(
|
||||||
character.c.user == user_id,
|
and_(character.c.user == user_id, character.c.characterId == character_id)
|
||||||
character.c.characterId == character_id
|
)
|
||||||
))
|
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.fetchone()
|
return result.fetchone()
|
||||||
|
|
||||||
def get_characters(self, user_id: int) -> Optional[List[Row]]:
|
def get_characters(self, user_id: int) -> Optional[List[Row]]:
|
||||||
sql = select(character).where(character.c.user == user_id)
|
sql = select(character).where(character.c.user == user_id)
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.fetchall()
|
return result.fetchall()
|
||||||
|
|
||||||
def put_item(self, user_id: int, item_data: Dict) -> Optional[int]:
|
def put_item(self, user_id: int, item_data: Dict) -> Optional[int]:
|
||||||
@@ -133,20 +156,21 @@ class ChuniItemData(BaseData):
|
|||||||
conflict = sql.on_duplicate_key_update(**item_data)
|
conflict = sql.on_duplicate_key_update(**item_data)
|
||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
def get_items(self, user_id: int, kind: int = None) -> Optional[List[Row]]:
|
def get_items(self, user_id: int, kind: int = None) -> Optional[List[Row]]:
|
||||||
if kind is None:
|
if kind is None:
|
||||||
sql = select(item).where(item.c.user == user_id)
|
sql = select(item).where(item.c.user == user_id)
|
||||||
else:
|
else:
|
||||||
sql = select(item).where(and_(
|
sql = select(item).where(
|
||||||
item.c.user == user_id,
|
and_(item.c.user == user_id, item.c.itemKind == kind)
|
||||||
item.c.itemKind == kind
|
)
|
||||||
))
|
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.fetchall()
|
return result.fetchall()
|
||||||
|
|
||||||
def put_duel(self, user_id: int, duel_data: Dict) -> Optional[int]:
|
def put_duel(self, user_id: int, duel_data: Dict) -> Optional[int]:
|
||||||
@@ -158,14 +182,16 @@ class ChuniItemData(BaseData):
|
|||||||
conflict = sql.on_duplicate_key_update(**duel_data)
|
conflict = sql.on_duplicate_key_update(**duel_data)
|
||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
def get_duels(self, user_id: int) -> Optional[List[Row]]:
|
def get_duels(self, user_id: int) -> Optional[List[Row]]:
|
||||||
sql = select(duel).where(duel.c.user == user_id)
|
sql = select(duel).where(duel.c.user == user_id)
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.fetchall()
|
return result.fetchall()
|
||||||
|
|
||||||
def put_map(self, user_id: int, map_data: Dict) -> Optional[int]:
|
def put_map(self, user_id: int, map_data: Dict) -> Optional[int]:
|
||||||
@@ -177,14 +203,16 @@ class ChuniItemData(BaseData):
|
|||||||
conflict = sql.on_duplicate_key_update(**map_data)
|
conflict = sql.on_duplicate_key_update(**map_data)
|
||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
def get_maps(self, user_id: int) -> Optional[List[Row]]:
|
def get_maps(self, user_id: int) -> Optional[List[Row]]:
|
||||||
sql = select(map).where(map.c.user == user_id)
|
sql = select(map).where(map.c.user == user_id)
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.fetchall()
|
return result.fetchall()
|
||||||
|
|
||||||
def put_map_area(self, user_id: int, map_area_data: Dict) -> Optional[int]:
|
def put_map_area(self, user_id: int, map_area_data: Dict) -> Optional[int]:
|
||||||
@@ -196,12 +224,14 @@ class ChuniItemData(BaseData):
|
|||||||
conflict = sql.on_duplicate_key_update(**map_area_data)
|
conflict = sql.on_duplicate_key_update(**map_area_data)
|
||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
def get_map_areas(self, user_id: int) -> Optional[List[Row]]:
|
def get_map_areas(self, user_id: int) -> Optional[List[Row]]:
|
||||||
sql = select(map_area).where(map_area.c.user == user_id)
|
sql = select(map_area).where(map_area.c.user == user_id)
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.fetchall()
|
return result.fetchall()
|
||||||
+144
-59
@@ -13,7 +13,11 @@ profile = Table(
|
|||||||
"chuni_profile_data",
|
"chuni_profile_data",
|
||||||
metadata,
|
metadata,
|
||||||
Column("id", Integer, primary_key=True, nullable=False),
|
Column("id", Integer, primary_key=True, nullable=False),
|
||||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
Column(
|
||||||
|
"user",
|
||||||
|
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
Column("version", Integer, nullable=False),
|
Column("version", Integer, nullable=False),
|
||||||
Column("exp", Integer),
|
Column("exp", Integer),
|
||||||
Column("level", Integer),
|
Column("level", Integer),
|
||||||
@@ -80,7 +84,11 @@ profile = Table(
|
|||||||
Column("compatibleCmVersion", String(25)),
|
Column("compatibleCmVersion", String(25)),
|
||||||
Column("medal", Integer),
|
Column("medal", Integer),
|
||||||
Column("voiceId", Integer),
|
Column("voiceId", Integer),
|
||||||
Column("teamId", Integer, ForeignKey("chuni_profile_team.id", ondelete="SET NULL", onupdate="SET NULL")),
|
Column(
|
||||||
|
"teamId",
|
||||||
|
Integer,
|
||||||
|
ForeignKey("chuni_profile_team.id", ondelete="SET NULL", onupdate="SET NULL"),
|
||||||
|
),
|
||||||
Column("avatarBack", Integer, server_default="0"),
|
Column("avatarBack", Integer, server_default="0"),
|
||||||
Column("avatarFace", Integer, server_default="0"),
|
Column("avatarFace", Integer, server_default="0"),
|
||||||
Column("eliteRankPoint", Integer, server_default="0"),
|
Column("eliteRankPoint", Integer, server_default="0"),
|
||||||
@@ -121,14 +129,18 @@ profile = Table(
|
|||||||
Column("netBattleEndState", Integer, server_default="0"),
|
Column("netBattleEndState", Integer, server_default="0"),
|
||||||
Column("avatarHead", Integer, server_default="0"),
|
Column("avatarHead", Integer, server_default="0"),
|
||||||
UniqueConstraint("user", "version", name="chuni_profile_profile_uk"),
|
UniqueConstraint("user", "version", name="chuni_profile_profile_uk"),
|
||||||
mysql_charset='utf8mb4'
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
profile_ex = Table(
|
profile_ex = Table(
|
||||||
"chuni_profile_data_ex",
|
"chuni_profile_data_ex",
|
||||||
metadata,
|
metadata,
|
||||||
Column("id", Integer, primary_key=True, nullable=False),
|
Column("id", Integer, primary_key=True, nullable=False),
|
||||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
Column(
|
||||||
|
"user",
|
||||||
|
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
Column("version", Integer, nullable=False),
|
Column("version", Integer, nullable=False),
|
||||||
Column("ext1", Integer),
|
Column("ext1", Integer),
|
||||||
Column("ext2", Integer),
|
Column("ext2", Integer),
|
||||||
@@ -165,14 +177,18 @@ profile_ex = Table(
|
|||||||
Column("mapIconId", Integer),
|
Column("mapIconId", Integer),
|
||||||
Column("compatibleCmVersion", String(25)),
|
Column("compatibleCmVersion", String(25)),
|
||||||
UniqueConstraint("user", "version", name="chuni_profile_data_ex_uk"),
|
UniqueConstraint("user", "version", name="chuni_profile_data_ex_uk"),
|
||||||
mysql_charset='utf8mb4'
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
option = Table(
|
option = Table(
|
||||||
"chuni_profile_option",
|
"chuni_profile_option",
|
||||||
metadata,
|
metadata,
|
||||||
Column("id", Integer, primary_key=True, nullable=False),
|
Column("id", Integer, primary_key=True, nullable=False),
|
||||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
Column(
|
||||||
|
"user",
|
||||||
|
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
Column("speed", Integer),
|
Column("speed", Integer),
|
||||||
Column("bgInfo", Integer),
|
Column("bgInfo", Integer),
|
||||||
Column("rating", Integer),
|
Column("rating", Integer),
|
||||||
@@ -224,14 +240,18 @@ option = Table(
|
|||||||
Column("playTimingOffset", Integer, server_default="0"),
|
Column("playTimingOffset", Integer, server_default="0"),
|
||||||
Column("fieldWallPosition_120", Integer, server_default="0"),
|
Column("fieldWallPosition_120", Integer, server_default="0"),
|
||||||
UniqueConstraint("user", name="chuni_profile_option_uk"),
|
UniqueConstraint("user", name="chuni_profile_option_uk"),
|
||||||
mysql_charset='utf8mb4'
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
option_ex = Table(
|
option_ex = Table(
|
||||||
"chuni_profile_option_ex",
|
"chuni_profile_option_ex",
|
||||||
metadata,
|
metadata,
|
||||||
Column("id", Integer, primary_key=True, nullable=False),
|
Column("id", Integer, primary_key=True, nullable=False),
|
||||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
Column(
|
||||||
|
"user",
|
||||||
|
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
Column("ext1", Integer),
|
Column("ext1", Integer),
|
||||||
Column("ext2", Integer),
|
Column("ext2", Integer),
|
||||||
Column("ext3", Integer),
|
Column("ext3", Integer),
|
||||||
@@ -253,51 +273,69 @@ option_ex = Table(
|
|||||||
Column("ext19", Integer),
|
Column("ext19", Integer),
|
||||||
Column("ext20", Integer),
|
Column("ext20", Integer),
|
||||||
UniqueConstraint("user", name="chuni_profile_option_ex_uk"),
|
UniqueConstraint("user", name="chuni_profile_option_ex_uk"),
|
||||||
mysql_charset='utf8mb4'
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
recent_rating = Table(
|
recent_rating = Table(
|
||||||
"chuni_profile_recent_rating",
|
"chuni_profile_recent_rating",
|
||||||
metadata,
|
metadata,
|
||||||
Column("id", Integer, primary_key=True, nullable=False),
|
Column("id", Integer, primary_key=True, nullable=False),
|
||||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
Column(
|
||||||
|
"user",
|
||||||
|
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
Column("recentRating", JSON),
|
Column("recentRating", JSON),
|
||||||
UniqueConstraint("user", name="chuni_profile_recent_rating_uk"),
|
UniqueConstraint("user", name="chuni_profile_recent_rating_uk"),
|
||||||
mysql_charset='utf8mb4'
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
region = Table(
|
region = Table(
|
||||||
"chuni_profile_region",
|
"chuni_profile_region",
|
||||||
metadata,
|
metadata,
|
||||||
Column("id", Integer, primary_key=True, nullable=False),
|
Column("id", Integer, primary_key=True, nullable=False),
|
||||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
Column(
|
||||||
|
"user",
|
||||||
|
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
Column("regionId", Integer),
|
Column("regionId", Integer),
|
||||||
Column("playCount", Integer),
|
Column("playCount", Integer),
|
||||||
UniqueConstraint("user", "regionId", name="chuni_profile_region_uk"),
|
UniqueConstraint("user", "regionId", name="chuni_profile_region_uk"),
|
||||||
mysql_charset='utf8mb4'
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
activity = Table(
|
activity = Table(
|
||||||
"chuni_profile_activity",
|
"chuni_profile_activity",
|
||||||
metadata,
|
metadata,
|
||||||
Column("id", Integer, primary_key=True, nullable=False),
|
Column("id", Integer, primary_key=True, nullable=False),
|
||||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
Column(
|
||||||
|
"user",
|
||||||
|
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
Column("kind", Integer),
|
Column("kind", Integer),
|
||||||
Column("activityId", Integer), # Reminder: Change this to ID in base.py or the game will be sad
|
Column(
|
||||||
|
"activityId", Integer
|
||||||
|
), # Reminder: Change this to ID in base.py or the game will be sad
|
||||||
Column("sortNumber", Integer),
|
Column("sortNumber", Integer),
|
||||||
Column("param1", Integer),
|
Column("param1", Integer),
|
||||||
Column("param2", Integer),
|
Column("param2", Integer),
|
||||||
Column("param3", Integer),
|
Column("param3", Integer),
|
||||||
Column("param4", Integer),
|
Column("param4", Integer),
|
||||||
UniqueConstraint("user", "kind", "activityId", name="chuni_profile_activity_uk"),
|
UniqueConstraint("user", "kind", "activityId", name="chuni_profile_activity_uk"),
|
||||||
mysql_charset='utf8mb4'
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
charge = Table(
|
charge = Table(
|
||||||
"chuni_profile_charge",
|
"chuni_profile_charge",
|
||||||
metadata,
|
metadata,
|
||||||
Column("id", Integer, primary_key=True, nullable=False),
|
Column("id", Integer, primary_key=True, nullable=False),
|
||||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
Column(
|
||||||
|
"user",
|
||||||
|
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
Column("chargeId", Integer),
|
Column("chargeId", Integer),
|
||||||
Column("stock", Integer),
|
Column("stock", Integer),
|
||||||
Column("purchaseDate", String(25)),
|
Column("purchaseDate", String(25)),
|
||||||
@@ -306,14 +344,18 @@ charge = Table(
|
|||||||
Column("param2", Integer),
|
Column("param2", Integer),
|
||||||
Column("paramDate", String(25)),
|
Column("paramDate", String(25)),
|
||||||
UniqueConstraint("user", "chargeId", name="chuni_profile_charge_uk"),
|
UniqueConstraint("user", "chargeId", name="chuni_profile_charge_uk"),
|
||||||
mysql_charset='utf8mb4'
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
emoney = Table(
|
emoney = Table(
|
||||||
"chuni_profile_emoney",
|
"chuni_profile_emoney",
|
||||||
metadata,
|
metadata,
|
||||||
Column("id", Integer, primary_key=True, nullable=False),
|
Column("id", Integer, primary_key=True, nullable=False),
|
||||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
Column(
|
||||||
|
"user",
|
||||||
|
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
Column("ext1", Integer),
|
Column("ext1", Integer),
|
||||||
Column("ext2", Integer),
|
Column("ext2", Integer),
|
||||||
Column("ext3", Integer),
|
Column("ext3", Integer),
|
||||||
@@ -321,20 +363,24 @@ emoney = Table(
|
|||||||
Column("emoneyBrand", Integer),
|
Column("emoneyBrand", Integer),
|
||||||
Column("emoneyCredit", Integer),
|
Column("emoneyCredit", Integer),
|
||||||
UniqueConstraint("user", "emoneyBrand", name="chuni_profile_emoney_uk"),
|
UniqueConstraint("user", "emoneyBrand", name="chuni_profile_emoney_uk"),
|
||||||
mysql_charset='utf8mb4'
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
overpower = Table(
|
overpower = Table(
|
||||||
"chuni_profile_overpower",
|
"chuni_profile_overpower",
|
||||||
metadata,
|
metadata,
|
||||||
Column("id", Integer, primary_key=True, nullable=False),
|
Column("id", Integer, primary_key=True, nullable=False),
|
||||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
Column(
|
||||||
|
"user",
|
||||||
|
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
Column("genreId", Integer),
|
Column("genreId", Integer),
|
||||||
Column("difficulty", Integer),
|
Column("difficulty", Integer),
|
||||||
Column("rate", Integer),
|
Column("rate", Integer),
|
||||||
Column("point", Integer),
|
Column("point", Integer),
|
||||||
UniqueConstraint("user", "genreId", "difficulty", name="chuni_profile_emoney_uk"),
|
UniqueConstraint("user", "genreId", "difficulty", name="chuni_profile_emoney_uk"),
|
||||||
mysql_charset='utf8mb4'
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
team = Table(
|
team = Table(
|
||||||
@@ -343,11 +389,14 @@ team = Table(
|
|||||||
Column("id", Integer, primary_key=True, nullable=False),
|
Column("id", Integer, primary_key=True, nullable=False),
|
||||||
Column("teamName", String(255)),
|
Column("teamName", String(255)),
|
||||||
Column("teamPoint", Integer),
|
Column("teamPoint", Integer),
|
||||||
mysql_charset='utf8mb4'
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ChuniProfileData(BaseData):
|
class ChuniProfileData(BaseData):
|
||||||
def put_profile_data(self, aime_id: int, version: int, profile_data: Dict) -> Optional[int]:
|
def put_profile_data(
|
||||||
|
self, aime_id: int, version: int, profile_data: Dict
|
||||||
|
) -> Optional[int]:
|
||||||
profile_data["user"] = aime_id
|
profile_data["user"] = aime_id
|
||||||
profile_data["version"] = version
|
profile_data["version"] = version
|
||||||
if "accessCode" in profile_data:
|
if "accessCode" in profile_data:
|
||||||
@@ -365,25 +414,33 @@ class ChuniProfileData(BaseData):
|
|||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
def get_profile_preview(self, aime_id: int, version: int) -> Optional[Row]:
|
def get_profile_preview(self, aime_id: int, version: int) -> Optional[Row]:
|
||||||
sql = select([profile, option]).join(option, profile.c.user == option.c.user).filter(
|
sql = (
|
||||||
and_(profile.c.user == aime_id, profile.c.version == version)
|
select([profile, option])
|
||||||
|
.join(option, profile.c.user == option.c.user)
|
||||||
|
.filter(and_(profile.c.user == aime_id, profile.c.version == version))
|
||||||
)
|
)
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.fetchone()
|
return result.fetchone()
|
||||||
|
|
||||||
def get_profile_data(self, aime_id: int, version: int) -> Optional[Row]:
|
def get_profile_data(self, aime_id: int, version: int) -> Optional[Row]:
|
||||||
sql = select(profile).where(and_(
|
sql = select(profile).where(
|
||||||
|
and_(
|
||||||
profile.c.user == aime_id,
|
profile.c.user == aime_id,
|
||||||
profile.c.version == version,
|
profile.c.version == version,
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.fetchone()
|
return result.fetchone()
|
||||||
|
|
||||||
def put_profile_data_ex(self, aime_id: int, version: int, profile_ex_data: Dict) -> Optional[int]:
|
def put_profile_data_ex(
|
||||||
|
self, aime_id: int, version: int, profile_ex_data: Dict
|
||||||
|
) -> Optional[int]:
|
||||||
profile_ex_data["user"] = aime_id
|
profile_ex_data["user"] = aime_id
|
||||||
profile_ex_data["version"] = version
|
profile_ex_data["version"] = version
|
||||||
if "accessCode" in profile_ex_data:
|
if "accessCode" in profile_ex_data:
|
||||||
@@ -394,18 +451,23 @@ class ChuniProfileData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_profile_data_ex: Failed to update! aime_id: {aime_id}")
|
self.logger.warn(
|
||||||
|
f"put_profile_data_ex: Failed to update! aime_id: {aime_id}"
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
def get_profile_data_ex(self, aime_id: int, version: int) -> Optional[Row]:
|
def get_profile_data_ex(self, aime_id: int, version: int) -> Optional[Row]:
|
||||||
sql = select(profile_ex).where(and_(
|
sql = select(profile_ex).where(
|
||||||
|
and_(
|
||||||
profile_ex.c.user == aime_id,
|
profile_ex.c.user == aime_id,
|
||||||
profile_ex.c.version == version,
|
profile_ex.c.version == version,
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.fetchone()
|
return result.fetchone()
|
||||||
|
|
||||||
def put_profile_option(self, aime_id: int, option_data: Dict) -> Optional[int]:
|
def put_profile_option(self, aime_id: int, option_data: Dict) -> Optional[int]:
|
||||||
@@ -416,7 +478,9 @@ class ChuniProfileData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_profile_option: Failed to update! aime_id: {aime_id}")
|
self.logger.warn(
|
||||||
|
f"put_profile_option: Failed to update! aime_id: {aime_id}"
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -424,10 +488,13 @@ class ChuniProfileData(BaseData):
|
|||||||
sql = select(option).where(option.c.user == aime_id)
|
sql = select(option).where(option.c.user == aime_id)
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.fetchone()
|
return result.fetchone()
|
||||||
|
|
||||||
def put_profile_option_ex(self, aime_id: int, option_ex_data: Dict) -> Optional[int]:
|
def put_profile_option_ex(
|
||||||
|
self, aime_id: int, option_ex_data: Dict
|
||||||
|
) -> Optional[int]:
|
||||||
option_ex_data["user"] = aime_id
|
option_ex_data["user"] = aime_id
|
||||||
|
|
||||||
sql = insert(option_ex).values(**option_ex_data)
|
sql = insert(option_ex).values(**option_ex_data)
|
||||||
@@ -435,7 +502,9 @@ class ChuniProfileData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_profile_option_ex: Failed to update! aime_id: {aime_id}")
|
self.logger.warn(
|
||||||
|
f"put_profile_option_ex: Failed to update! aime_id: {aime_id}"
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -443,19 +512,23 @@ class ChuniProfileData(BaseData):
|
|||||||
sql = select(option_ex).where(option_ex.c.user == aime_id)
|
sql = select(option_ex).where(option_ex.c.user == aime_id)
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.fetchone()
|
return result.fetchone()
|
||||||
|
|
||||||
def put_profile_recent_rating(self, aime_id: int, recent_rating_data: List[Dict]) -> Optional[int]:
|
def put_profile_recent_rating(
|
||||||
|
self, aime_id: int, recent_rating_data: List[Dict]
|
||||||
|
) -> Optional[int]:
|
||||||
sql = insert(recent_rating).values(
|
sql = insert(recent_rating).values(
|
||||||
user = aime_id,
|
user=aime_id, recentRating=recent_rating_data
|
||||||
recentRating = recent_rating_data
|
|
||||||
)
|
)
|
||||||
conflict = sql.on_duplicate_key_update(recentRating = recent_rating_data)
|
conflict = sql.on_duplicate_key_update(recentRating=recent_rating_data)
|
||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_profile_recent_rating: Failed to update! aime_id: {aime_id}")
|
self.logger.warn(
|
||||||
|
f"put_profile_recent_rating: Failed to update! aime_id: {aime_id}"
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -463,7 +536,8 @@ class ChuniProfileData(BaseData):
|
|||||||
sql = select(recent_rating).where(recent_rating.c.user == aime_id)
|
sql = select(recent_rating).where(recent_rating.c.user == aime_id)
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.fetchone()
|
return result.fetchone()
|
||||||
|
|
||||||
def put_profile_activity(self, aime_id: int, activity_data: Dict) -> Optional[int]:
|
def put_profile_activity(self, aime_id: int, activity_data: Dict) -> Optional[int]:
|
||||||
@@ -477,18 +551,20 @@ class ChuniProfileData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_profile_activity: Failed to update! aime_id: {aime_id}")
|
self.logger.warn(
|
||||||
|
f"put_profile_activity: Failed to update! aime_id: {aime_id}"
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
def get_profile_activity(self, aime_id: int, kind: int) -> Optional[List[Row]]:
|
def get_profile_activity(self, aime_id: int, kind: int) -> Optional[List[Row]]:
|
||||||
sql = select(activity).where(and_(
|
sql = select(activity).where(
|
||||||
activity.c.user == aime_id,
|
and_(activity.c.user == aime_id, activity.c.kind == kind)
|
||||||
activity.c.kind == kind
|
)
|
||||||
))
|
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.fetchall()
|
return result.fetchall()
|
||||||
|
|
||||||
def put_profile_charge(self, aime_id: int, charge_data: Dict) -> Optional[int]:
|
def put_profile_charge(self, aime_id: int, charge_data: Dict) -> Optional[int]:
|
||||||
@@ -499,7 +575,9 @@ class ChuniProfileData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_profile_charge: Failed to update! aime_id: {aime_id}")
|
self.logger.warn(
|
||||||
|
f"put_profile_charge: Failed to update! aime_id: {aime_id}"
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -507,7 +585,8 @@ class ChuniProfileData(BaseData):
|
|||||||
sql = select(charge).where(charge.c.user == aime_id)
|
sql = select(charge).where(charge.c.user == aime_id)
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.fetchall()
|
return result.fetchall()
|
||||||
|
|
||||||
def add_profile_region(self, aime_id: int, region_id: int) -> Optional[int]:
|
def add_profile_region(self, aime_id: int, region_id: int) -> Optional[int]:
|
||||||
@@ -523,29 +602,35 @@ class ChuniProfileData(BaseData):
|
|||||||
conflict = sql.on_duplicate_key_update(**emoney_data)
|
conflict = sql.on_duplicate_key_update(**emoney_data)
|
||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
def get_profile_emoney(self, aime_id: int) -> Optional[List[Row]]:
|
def get_profile_emoney(self, aime_id: int) -> Optional[List[Row]]:
|
||||||
sql = select(emoney).where(emoney.c.user == aime_id)
|
sql = select(emoney).where(emoney.c.user == aime_id)
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.fetchall()
|
return result.fetchall()
|
||||||
|
|
||||||
def put_profile_overpower(self, aime_id: int, overpower_data: Dict) -> Optional[int]:
|
def put_profile_overpower(
|
||||||
|
self, aime_id: int, overpower_data: Dict
|
||||||
|
) -> Optional[int]:
|
||||||
overpower_data["user"] = aime_id
|
overpower_data["user"] = aime_id
|
||||||
|
|
||||||
sql = insert(overpower).values(**overpower_data)
|
sql = insert(overpower).values(**overpower_data)
|
||||||
conflict = sql.on_duplicate_key_update(**overpower_data)
|
conflict = sql.on_duplicate_key_update(**overpower_data)
|
||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
def get_profile_overpower(self, aime_id: int) -> Optional[List[Row]]:
|
def get_profile_overpower(self, aime_id: int) -> Optional[List[Row]]:
|
||||||
sql = select(overpower).where(overpower.c.user == aime_id)
|
sql = select(overpower).where(overpower.c.user == aime_id)
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.fetchall()
|
return result.fetchall()
|
||||||
|
|||||||
@@ -13,7 +13,11 @@ course = Table(
|
|||||||
"chuni_score_course",
|
"chuni_score_course",
|
||||||
metadata,
|
metadata,
|
||||||
Column("id", Integer, primary_key=True, nullable=False),
|
Column("id", Integer, primary_key=True, nullable=False),
|
||||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
Column(
|
||||||
|
"user",
|
||||||
|
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
Column("courseId", Integer),
|
Column("courseId", Integer),
|
||||||
Column("classId", Integer),
|
Column("classId", Integer),
|
||||||
Column("playCount", Integer),
|
Column("playCount", Integer),
|
||||||
@@ -33,14 +37,18 @@ course = Table(
|
|||||||
Column("orderId", Integer),
|
Column("orderId", Integer),
|
||||||
Column("playerRating", Integer),
|
Column("playerRating", Integer),
|
||||||
UniqueConstraint("user", "courseId", name="chuni_score_course_uk"),
|
UniqueConstraint("user", "courseId", name="chuni_score_course_uk"),
|
||||||
mysql_charset='utf8mb4'
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
best_score = Table(
|
best_score = Table(
|
||||||
"chuni_score_best",
|
"chuni_score_best",
|
||||||
metadata,
|
metadata,
|
||||||
Column("id", Integer, primary_key=True, nullable=False),
|
Column("id", Integer, primary_key=True, nullable=False),
|
||||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
Column(
|
||||||
|
"user",
|
||||||
|
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
Column("musicId", Integer),
|
Column("musicId", Integer),
|
||||||
Column("level", Integer),
|
Column("level", Integer),
|
||||||
Column("playCount", Integer),
|
Column("playCount", Integer),
|
||||||
@@ -60,14 +68,18 @@ best_score = Table(
|
|||||||
Column("ext1", Integer),
|
Column("ext1", Integer),
|
||||||
Column("theoryCount", Integer),
|
Column("theoryCount", Integer),
|
||||||
UniqueConstraint("user", "musicId", "level", name="chuni_score_best_uk"),
|
UniqueConstraint("user", "musicId", "level", name="chuni_score_best_uk"),
|
||||||
mysql_charset='utf8mb4'
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
playlog = Table(
|
playlog = Table(
|
||||||
"chuni_score_playlog",
|
"chuni_score_playlog",
|
||||||
metadata,
|
metadata,
|
||||||
Column("id", Integer, primary_key=True, nullable=False),
|
Column("id", Integer, primary_key=True, nullable=False),
|
||||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
Column(
|
||||||
|
"user",
|
||||||
|
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
Column("orderId", Integer),
|
Column("orderId", Integer),
|
||||||
Column("sortNumber", Integer),
|
Column("sortNumber", Integer),
|
||||||
Column("placeId", Integer),
|
Column("placeId", Integer),
|
||||||
@@ -122,15 +134,17 @@ playlog = Table(
|
|||||||
Column("charaIllustId", Integer),
|
Column("charaIllustId", Integer),
|
||||||
Column("romVersion", String(255)),
|
Column("romVersion", String(255)),
|
||||||
Column("judgeHeaven", Integer),
|
Column("judgeHeaven", Integer),
|
||||||
mysql_charset='utf8mb4'
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ChuniScoreData(BaseData):
|
class ChuniScoreData(BaseData):
|
||||||
def get_courses(self, aime_id: int) -> Optional[Row]:
|
def get_courses(self, aime_id: int) -> Optional[Row]:
|
||||||
sql = select(course).where(course.c.user == aime_id)
|
sql = select(course).where(course.c.user == aime_id)
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.fetchall()
|
return result.fetchall()
|
||||||
|
|
||||||
def put_course(self, aime_id: int, course_data: Dict) -> Optional[int]:
|
def put_course(self, aime_id: int, course_data: Dict) -> Optional[int]:
|
||||||
@@ -141,14 +155,16 @@ class ChuniScoreData(BaseData):
|
|||||||
conflict = sql.on_duplicate_key_update(**course_data)
|
conflict = sql.on_duplicate_key_update(**course_data)
|
||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
def get_scores(self, aime_id: int) -> Optional[Row]:
|
def get_scores(self, aime_id: int) -> Optional[Row]:
|
||||||
sql = select(best_score).where(best_score.c.user == aime_id)
|
sql = select(best_score).where(best_score.c.user == aime_id)
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.fetchall()
|
return result.fetchall()
|
||||||
|
|
||||||
def put_score(self, aime_id: int, score_data: Dict) -> Optional[int]:
|
def put_score(self, aime_id: int, score_data: Dict) -> Optional[int]:
|
||||||
@@ -159,14 +175,16 @@ class ChuniScoreData(BaseData):
|
|||||||
conflict = sql.on_duplicate_key_update(**score_data)
|
conflict = sql.on_duplicate_key_update(**score_data)
|
||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
def get_playlogs(self, aime_id: int) -> Optional[Row]:
|
def get_playlogs(self, aime_id: int) -> Optional[Row]:
|
||||||
sql = select(playlog).where(playlog.c.user == aime_id)
|
sql = select(playlog).where(playlog.c.user == aime_id)
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.fetchall()
|
return result.fetchall()
|
||||||
|
|
||||||
def put_playlog(self, aime_id: int, playlog_data: Dict) -> Optional[int]:
|
def put_playlog(self, aime_id: int, playlog_data: Dict) -> Optional[int]:
|
||||||
@@ -177,5 +195,6 @@ class ChuniScoreData(BaseData):
|
|||||||
conflict = sql.on_duplicate_key_update(**playlog_data)
|
conflict = sql.on_duplicate_key_update(**playlog_data)
|
||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|||||||
+124
-80
@@ -19,7 +19,7 @@ events = Table(
|
|||||||
Column("name", String(255)),
|
Column("name", String(255)),
|
||||||
Column("enabled", Boolean, server_default="1"),
|
Column("enabled", Boolean, server_default="1"),
|
||||||
UniqueConstraint("version", "eventId", name="chuni_static_events_uk"),
|
UniqueConstraint("version", "eventId", name="chuni_static_events_uk"),
|
||||||
mysql_charset='utf8mb4'
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
music = Table(
|
music = Table(
|
||||||
@@ -36,7 +36,7 @@ music = Table(
|
|||||||
Column("jacketPath", String(255)),
|
Column("jacketPath", String(255)),
|
||||||
Column("worldsEndTag", String(7)),
|
Column("worldsEndTag", String(7)),
|
||||||
UniqueConstraint("version", "songId", "chartId", name="chuni_static_music_uk"),
|
UniqueConstraint("version", "songId", "chartId", name="chuni_static_music_uk"),
|
||||||
mysql_charset='utf8mb4'
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
charge = Table(
|
charge = Table(
|
||||||
@@ -51,7 +51,7 @@ charge = Table(
|
|||||||
Column("sellingAppeal", Boolean),
|
Column("sellingAppeal", Boolean),
|
||||||
Column("enabled", Boolean, server_default="1"),
|
Column("enabled", Boolean, server_default="1"),
|
||||||
UniqueConstraint("version", "chargeId", name="chuni_static_charge_uk"),
|
UniqueConstraint("version", "chargeId", name="chuni_static_charge_uk"),
|
||||||
mysql_charset='utf8mb4'
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
avatar = Table(
|
avatar = Table(
|
||||||
@@ -65,159 +65,203 @@ avatar = Table(
|
|||||||
Column("iconPath", String(255)),
|
Column("iconPath", String(255)),
|
||||||
Column("texturePath", String(255)),
|
Column("texturePath", String(255)),
|
||||||
UniqueConstraint("version", "avatarAccessoryId", name="chuni_static_avatar_uk"),
|
UniqueConstraint("version", "avatarAccessoryId", name="chuni_static_avatar_uk"),
|
||||||
mysql_charset='utf8mb4'
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ChuniStaticData(BaseData):
|
class ChuniStaticData(BaseData):
|
||||||
def put_event(self, version: int, event_id: int, type: int, name: str) -> Optional[int]:
|
def put_event(
|
||||||
|
self, version: int, event_id: int, type: int, name: str
|
||||||
|
) -> Optional[int]:
|
||||||
sql = insert(events).values(
|
sql = insert(events).values(
|
||||||
version = version,
|
version=version, eventId=event_id, type=type, name=name
|
||||||
eventId = event_id,
|
|
||||||
type = type,
|
|
||||||
name = name
|
|
||||||
)
|
)
|
||||||
|
|
||||||
conflict = sql.on_duplicate_key_update(
|
conflict = sql.on_duplicate_key_update(name=name)
|
||||||
name = name
|
|
||||||
)
|
|
||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
def update_event(self, version: int, event_id: int, enabled: bool) -> Optional[bool]:
|
def update_event(
|
||||||
sql = events.update(and_(events.c.version == version, events.c.eventId == event_id)).values(
|
self, version: int, event_id: int, enabled: bool
|
||||||
enabled = enabled
|
) -> Optional[bool]:
|
||||||
)
|
sql = events.update(
|
||||||
|
and_(events.c.version == version, events.c.eventId == event_id)
|
||||||
|
).values(enabled=enabled)
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"update_event: failed to update event! version: {version}, event_id: {event_id}, enabled: {enabled}")
|
self.logger.warn(
|
||||||
|
f"update_event: failed to update event! version: {version}, event_id: {event_id}, enabled: {enabled}"
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
event = self.get_event(version, event_id)
|
event = self.get_event(version, event_id)
|
||||||
if event is None:
|
if event is None:
|
||||||
self.logger.warn(f"update_event: failed to fetch event {event_id} after updating")
|
self.logger.warn(
|
||||||
|
f"update_event: failed to fetch event {event_id} after updating"
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
return event["enabled"]
|
return event["enabled"]
|
||||||
|
|
||||||
def get_event(self, version: int, event_id: int) -> Optional[Row]:
|
def get_event(self, version: int, event_id: int) -> Optional[Row]:
|
||||||
sql = select(events).where(and_(events.c.version == version, events.c.eventId == event_id))
|
sql = select(events).where(
|
||||||
|
and_(events.c.version == version, events.c.eventId == event_id)
|
||||||
|
)
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.fetchone()
|
return result.fetchone()
|
||||||
|
|
||||||
def get_enabled_events(self, version: int) -> Optional[List[Row]]:
|
def get_enabled_events(self, version: int) -> Optional[List[Row]]:
|
||||||
sql = select(events).where(and_(events.c.version == version, events.c.enabled == True))
|
sql = select(events).where(
|
||||||
|
and_(events.c.version == version, events.c.enabled == True)
|
||||||
|
)
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.fetchall()
|
return result.fetchall()
|
||||||
|
|
||||||
def get_events(self, version: int) -> Optional[List[Row]]:
|
def get_events(self, version: int) -> Optional[List[Row]]:
|
||||||
sql = select(events).where(events.c.version == version)
|
sql = select(events).where(events.c.version == version)
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.fetchall()
|
return result.fetchall()
|
||||||
|
|
||||||
def put_music(self, version: int, song_id: int, chart_id: int, title: int, artist: str,
|
def put_music(
|
||||||
level: float, genre: str, jacketPath: str, we_tag: str) -> Optional[int]:
|
self,
|
||||||
|
version: int,
|
||||||
|
song_id: int,
|
||||||
|
chart_id: int,
|
||||||
|
title: int,
|
||||||
|
artist: str,
|
||||||
|
level: float,
|
||||||
|
genre: str,
|
||||||
|
jacketPath: str,
|
||||||
|
we_tag: str,
|
||||||
|
) -> Optional[int]:
|
||||||
sql = insert(music).values(
|
sql = insert(music).values(
|
||||||
version = version,
|
version=version,
|
||||||
songId = song_id,
|
songId=song_id,
|
||||||
chartId = chart_id,
|
chartId=chart_id,
|
||||||
title = title,
|
title=title,
|
||||||
artist = artist,
|
artist=artist,
|
||||||
level = level,
|
level=level,
|
||||||
genre = genre,
|
genre=genre,
|
||||||
jacketPath = jacketPath,
|
jacketPath=jacketPath,
|
||||||
worldsEndTag = we_tag,
|
worldsEndTag=we_tag,
|
||||||
)
|
)
|
||||||
|
|
||||||
conflict = sql.on_duplicate_key_update(
|
conflict = sql.on_duplicate_key_update(
|
||||||
title = title,
|
title=title,
|
||||||
artist = artist,
|
artist=artist,
|
||||||
level = level,
|
level=level,
|
||||||
genre = genre,
|
genre=genre,
|
||||||
jacketPath = jacketPath,
|
jacketPath=jacketPath,
|
||||||
worldsEndTag = we_tag,
|
worldsEndTag=we_tag,
|
||||||
)
|
)
|
||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
def put_charge(self, version: int, charge_id: int, name: str, expiration_days: int,
|
def put_charge(
|
||||||
consume_type: int, selling_appeal: bool) -> Optional[int]:
|
self,
|
||||||
|
version: int,
|
||||||
|
charge_id: int,
|
||||||
|
name: str,
|
||||||
|
expiration_days: int,
|
||||||
|
consume_type: int,
|
||||||
|
selling_appeal: bool,
|
||||||
|
) -> Optional[int]:
|
||||||
sql = insert(charge).values(
|
sql = insert(charge).values(
|
||||||
version = version,
|
version=version,
|
||||||
chargeId = charge_id,
|
chargeId=charge_id,
|
||||||
name = name,
|
name=name,
|
||||||
expirationDays = expiration_days,
|
expirationDays=expiration_days,
|
||||||
consumeType = consume_type,
|
consumeType=consume_type,
|
||||||
sellingAppeal = selling_appeal,
|
sellingAppeal=selling_appeal,
|
||||||
)
|
)
|
||||||
|
|
||||||
conflict = sql.on_duplicate_key_update(
|
conflict = sql.on_duplicate_key_update(
|
||||||
name = name,
|
name=name,
|
||||||
expirationDays = expiration_days,
|
expirationDays=expiration_days,
|
||||||
consumeType = consume_type,
|
consumeType=consume_type,
|
||||||
sellingAppeal = selling_appeal,
|
sellingAppeal=selling_appeal,
|
||||||
)
|
)
|
||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
def get_enabled_charges(self, version: int) -> Optional[List[Row]]:
|
def get_enabled_charges(self, version: int) -> Optional[List[Row]]:
|
||||||
sql = select(charge).where(and_(
|
sql = select(charge).where(
|
||||||
charge.c.version == version,
|
and_(charge.c.version == version, charge.c.enabled == True)
|
||||||
charge.c.enabled == True
|
)
|
||||||
))
|
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.fetchall()
|
return result.fetchall()
|
||||||
|
|
||||||
def get_charges(self, version: int) -> Optional[List[Row]]:
|
def get_charges(self, version: int) -> Optional[List[Row]]:
|
||||||
sql = select(charge).where(charge.c.version == version)
|
sql = select(charge).where(charge.c.version == version)
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.fetchall()
|
return result.fetchall()
|
||||||
|
|
||||||
def get_music_chart(self, version: int, song_id: int, chart_id: int) -> Optional[List[Row]]:
|
def get_music_chart(
|
||||||
sql = select(music).where(and_(
|
self, version: int, song_id: int, chart_id: int
|
||||||
|
) -> Optional[List[Row]]:
|
||||||
|
sql = select(music).where(
|
||||||
|
and_(
|
||||||
music.c.version == version,
|
music.c.version == version,
|
||||||
music.c.songId == song_id,
|
music.c.songId == song_id,
|
||||||
music.c.chartId == chart_id
|
music.c.chartId == chart_id,
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.fetchone()
|
return result.fetchone()
|
||||||
|
|
||||||
def put_avatar(self, version: int, avatarAccessoryId: int, name: str, category: int, iconPath: str, texturePath: str) -> Optional[int]:
|
def put_avatar(
|
||||||
|
self,
|
||||||
|
version: int,
|
||||||
|
avatarAccessoryId: int,
|
||||||
|
name: str,
|
||||||
|
category: int,
|
||||||
|
iconPath: str,
|
||||||
|
texturePath: str,
|
||||||
|
) -> Optional[int]:
|
||||||
sql = insert(avatar).values(
|
sql = insert(avatar).values(
|
||||||
version = version,
|
version=version,
|
||||||
avatarAccessoryId = avatarAccessoryId,
|
avatarAccessoryId=avatarAccessoryId,
|
||||||
name = name,
|
name=name,
|
||||||
category = category,
|
category=category,
|
||||||
iconPath = iconPath,
|
iconPath=iconPath,
|
||||||
texturePath = texturePath,
|
texturePath=texturePath,
|
||||||
)
|
)
|
||||||
|
|
||||||
conflict = sql.on_duplicate_key_update(
|
conflict = sql.on_duplicate_key_update(
|
||||||
name = name,
|
name=name,
|
||||||
category = category,
|
category=category,
|
||||||
iconPath = iconPath,
|
iconPath=iconPath,
|
||||||
texturePath = texturePath,
|
texturePath=texturePath,
|
||||||
)
|
)
|
||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None: return None
|
if result is None:
|
||||||
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from titles.chuni.base import ChuniBase
|
|||||||
from titles.chuni.const import ChuniConstants
|
from titles.chuni.const import ChuniConstants
|
||||||
from titles.chuni.config import ChuniConfig
|
from titles.chuni.config import ChuniConfig
|
||||||
|
|
||||||
|
|
||||||
class ChuniStar(ChuniBase):
|
class ChuniStar(ChuniBase):
|
||||||
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
|
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
|
||||||
super().__init__(core_cfg, game_cfg)
|
super().__init__(core_cfg, game_cfg)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from titles.chuni.base import ChuniBase
|
|||||||
from titles.chuni.const import ChuniConstants
|
from titles.chuni.const import ChuniConstants
|
||||||
from titles.chuni.config import ChuniConfig
|
from titles.chuni.config import ChuniConfig
|
||||||
|
|
||||||
|
|
||||||
class ChuniStarPlus(ChuniBase):
|
class ChuniStarPlus(ChuniBase):
|
||||||
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
|
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
|
||||||
super().__init__(core_cfg, game_cfg)
|
super().__init__(core_cfg, game_cfg)
|
||||||
|
|||||||
+20
-27
@@ -10,45 +10,42 @@ from titles.cm.const import CardMakerConstants
|
|||||||
from titles.cm.config import CardMakerConfig
|
from titles.cm.config import CardMakerConfig
|
||||||
|
|
||||||
|
|
||||||
class CardMakerBase():
|
class CardMakerBase:
|
||||||
def __init__(self, core_cfg: CoreConfig, game_cfg: CardMakerConfig) -> None:
|
def __init__(self, core_cfg: CoreConfig, game_cfg: CardMakerConfig) -> None:
|
||||||
self.core_cfg = core_cfg
|
self.core_cfg = core_cfg
|
||||||
self.game_cfg = game_cfg
|
self.game_cfg = game_cfg
|
||||||
self.date_time_format = "%Y-%m-%d %H:%M:%S"
|
self.date_time_format = "%Y-%m-%d %H:%M:%S"
|
||||||
self.date_time_format_ext = "%Y-%m-%d %H:%M:%S.%f" # needs to be lopped off at [:-5]
|
self.date_time_format_ext = (
|
||||||
|
"%Y-%m-%d %H:%M:%S.%f" # needs to be lopped off at [:-5]
|
||||||
|
)
|
||||||
self.date_time_format_short = "%Y-%m-%d"
|
self.date_time_format_short = "%Y-%m-%d"
|
||||||
self.logger = logging.getLogger("cardmaker")
|
self.logger = logging.getLogger("cardmaker")
|
||||||
self.game = CardMakerConstants.GAME_CODE
|
self.game = CardMakerConstants.GAME_CODE
|
||||||
self.version = CardMakerConstants.VER_CARD_MAKER
|
self.version = CardMakerConstants.VER_CARD_MAKER
|
||||||
|
|
||||||
def handle_get_game_connect_api_request(self, data: Dict) -> Dict:
|
def handle_get_game_connect_api_request(self, data: Dict) -> Dict:
|
||||||
|
if self.core_cfg.server.is_develop:
|
||||||
uri = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}"
|
uri = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}"
|
||||||
|
else:
|
||||||
|
uri = f"http://{self.core_cfg.title.hostname}"
|
||||||
|
|
||||||
# CHUNITHM = 0, maimai = 1, ONGEKI = 2
|
# CHUNITHM = 0, maimai = 1, ONGEKI = 2
|
||||||
return {
|
return {
|
||||||
"length": 3,
|
"length": 3,
|
||||||
"gameConnectList": [
|
"gameConnectList": [
|
||||||
{
|
{"modelKind": 0, "type": 1, "titleUri": f"{uri}/SDHD/200/"},
|
||||||
"modelKind": 0,
|
{"modelKind": 1, "type": 1, "titleUri": f"{uri}/SDEZ/120/"},
|
||||||
"type": 1,
|
{"modelKind": 2, "type": 1, "titleUri": f"{uri}/SDDT/130/"},
|
||||||
"titleUri": f"{uri}/SDHD/200/"
|
],
|
||||||
},
|
|
||||||
{
|
|
||||||
"modelKind": 1,
|
|
||||||
"type": 1,
|
|
||||||
"titleUri": f"{uri}/SDEZ/120/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"modelKind": 2,
|
|
||||||
"type": 1,
|
|
||||||
"titleUri": f"{uri}/SDDT/130/"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
|
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
|
||||||
reboot_start = date.strftime(datetime.now() + timedelta(hours=3), self.date_time_format)
|
reboot_start = date.strftime(
|
||||||
reboot_end = date.strftime(datetime.now() + timedelta(hours=4), self.date_time_format)
|
datetime.now() + timedelta(hours=3), self.date_time_format
|
||||||
|
)
|
||||||
|
reboot_end = date.strftime(
|
||||||
|
datetime.now() + timedelta(hours=4), self.date_time_format
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"gameSetting": {
|
"gameSetting": {
|
||||||
@@ -64,18 +61,14 @@ class CardMakerBase():
|
|||||||
"maxCountCard": 100,
|
"maxCountCard": 100,
|
||||||
"watermark": False,
|
"watermark": False,
|
||||||
"isMaintenance": False,
|
"isMaintenance": False,
|
||||||
"isBackgroundDistribute": False
|
"isBackgroundDistribute": False,
|
||||||
},
|
},
|
||||||
"isDumpUpload": False,
|
"isDumpUpload": False,
|
||||||
"isAou": False
|
"isAou": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
def handle_get_client_bookkeeping_api_request(self, data: Dict) -> Dict:
|
def handle_get_client_bookkeeping_api_request(self, data: Dict) -> Dict:
|
||||||
return {
|
return {"placeId": data["placeId"], "length": 0, "clientBookkeepingList": []}
|
||||||
"placeId": data["placeId"],
|
|
||||||
"length": 0,
|
|
||||||
"clientBookkeepingList": []
|
|
||||||
}
|
|
||||||
|
|
||||||
def handle_upsert_client_setting_api_request(self, data: Dict) -> Dict:
|
def handle_upsert_client_setting_api_request(self, data: Dict) -> Dict:
|
||||||
return {"returnCode": 1, "apiName": "UpsertClientSettingApi"}
|
return {"returnCode": 1, "apiName": "UpsertClientSettingApi"}
|
||||||
|
|||||||
+9
-21
@@ -17,29 +17,17 @@ class CardMaker136(CardMakerBase):
|
|||||||
self.version = CardMakerConstants.VER_CARD_MAKER_136
|
self.version = CardMakerConstants.VER_CARD_MAKER_136
|
||||||
|
|
||||||
def handle_get_game_connect_api_request(self, data: Dict) -> Dict:
|
def handle_get_game_connect_api_request(self, data: Dict) -> Dict:
|
||||||
|
ret = super().handle_get_game_connect_api_request(data)
|
||||||
|
if self.core_cfg.server.is_develop:
|
||||||
uri = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}"
|
uri = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}"
|
||||||
|
else:
|
||||||
|
uri = f"http://{self.core_cfg.title.hostname}"
|
||||||
|
|
||||||
# CHUNITHM = 0, maimai = 1, ONGEKI = 2
|
ret["gameConnectList"][0]["titleUri"] = f"{uri}/SDHD/205/"
|
||||||
return {
|
ret["gameConnectList"][1]["titleUri"] = f"{uri}/SDEZ/125/"
|
||||||
"length": 3,
|
ret["gameConnectList"][2]["titleUri"] = f"{uri}/SDDT/135/"
|
||||||
"gameConnectList": [
|
|
||||||
{
|
return ret
|
||||||
"modelKind": 0,
|
|
||||||
"type": 1,
|
|
||||||
"titleUri": f"{uri}/SDHD/205/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"modelKind": 1,
|
|
||||||
"type": 1,
|
|
||||||
"titleUri": f"{uri}/SDEZ/125/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"modelKind": 2,
|
|
||||||
"type": 1,
|
|
||||||
"titleUri": f"{uri}/SDDT/135/"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
|
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
|
||||||
ret = super().handle_get_game_setting_api_request(data)
|
ret = super().handle_get_game_setting_api_request(data)
|
||||||
|
|||||||
+9
-3
@@ -1,17 +1,23 @@
|
|||||||
from core.config import CoreConfig
|
from core.config import CoreConfig
|
||||||
|
|
||||||
|
|
||||||
class CardMakerServerConfig():
|
class CardMakerServerConfig:
|
||||||
def __init__(self, parent_config: "CardMakerConfig") -> None:
|
def __init__(self, parent_config: "CardMakerConfig") -> None:
|
||||||
self.__config = parent_config
|
self.__config = parent_config
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def enable(self) -> bool:
|
def enable(self) -> bool:
|
||||||
return CoreConfig.get_config_field(self.__config, 'cardmaker', 'server', 'enable', default=True)
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "cardmaker", "server", "enable", default=True
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def loglevel(self) -> int:
|
def loglevel(self) -> int:
|
||||||
return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'cardmaker', 'server', 'loglevel', default="info"))
|
return CoreConfig.str_to_loglevel(
|
||||||
|
CoreConfig.get_config_field(
|
||||||
|
self.__config, "cardmaker", "server", "loglevel", default="info"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CardMakerConfig(dict):
|
class CardMakerConfig(dict):
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
class CardMakerConstants():
|
class CardMakerConstants:
|
||||||
GAME_CODE = "SDED"
|
GAME_CODE = "SDED"
|
||||||
|
|
||||||
CONFIG_NAME = "cardmaker.yaml"
|
CONFIG_NAME = "cardmaker.yaml"
|
||||||
|
|||||||
+35
-21
@@ -18,23 +18,29 @@ from titles.cm.base import CardMakerBase
|
|||||||
from titles.cm.cm136 import CardMaker136
|
from titles.cm.cm136 import CardMaker136
|
||||||
|
|
||||||
|
|
||||||
class CardMakerServlet():
|
class CardMakerServlet:
|
||||||
def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None:
|
def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None:
|
||||||
self.core_cfg = core_cfg
|
self.core_cfg = core_cfg
|
||||||
self.game_cfg = CardMakerConfig()
|
self.game_cfg = CardMakerConfig()
|
||||||
if path.exists(f"{cfg_dir}/{CardMakerConstants.CONFIG_NAME}"):
|
if path.exists(f"{cfg_dir}/{CardMakerConstants.CONFIG_NAME}"):
|
||||||
self.game_cfg.update(yaml.safe_load(open(f"{cfg_dir}/{CardMakerConstants.CONFIG_NAME}")))
|
self.game_cfg.update(
|
||||||
|
yaml.safe_load(open(f"{cfg_dir}/{CardMakerConstants.CONFIG_NAME}"))
|
||||||
|
)
|
||||||
|
|
||||||
self.versions = [
|
self.versions = [
|
||||||
CardMakerBase(core_cfg, self.game_cfg),
|
CardMakerBase(core_cfg, self.game_cfg),
|
||||||
CardMaker136(core_cfg, self.game_cfg)
|
CardMaker136(core_cfg, self.game_cfg),
|
||||||
]
|
]
|
||||||
|
|
||||||
self.logger = logging.getLogger("cardmaker")
|
self.logger = logging.getLogger("cardmaker")
|
||||||
log_fmt_str = "[%(asctime)s] Card Maker | %(levelname)s | %(message)s"
|
log_fmt_str = "[%(asctime)s] Card Maker | %(levelname)s | %(message)s"
|
||||||
log_fmt = logging.Formatter(log_fmt_str)
|
log_fmt = logging.Formatter(log_fmt_str)
|
||||||
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.core_cfg.server.log_dir, "cardmaker"), encoding='utf8',
|
fileHandler = TimedRotatingFileHandler(
|
||||||
when="d", backupCount=10)
|
"{0}/{1}.log".format(self.core_cfg.server.log_dir, "cardmaker"),
|
||||||
|
encoding="utf8",
|
||||||
|
when="d",
|
||||||
|
backupCount=10,
|
||||||
|
)
|
||||||
|
|
||||||
fileHandler.setFormatter(log_fmt)
|
fileHandler.setFormatter(log_fmt)
|
||||||
|
|
||||||
@@ -45,20 +51,29 @@ class CardMakerServlet():
|
|||||||
self.logger.addHandler(consoleHandler)
|
self.logger.addHandler(consoleHandler)
|
||||||
|
|
||||||
self.logger.setLevel(self.game_cfg.server.loglevel)
|
self.logger.setLevel(self.game_cfg.server.loglevel)
|
||||||
coloredlogs.install(level=self.game_cfg.server.loglevel,
|
coloredlogs.install(
|
||||||
logger=self.logger, fmt=log_fmt_str)
|
level=self.game_cfg.server.loglevel, logger=self.logger, fmt=log_fmt_str
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_allnet_info(cls, game_code: str, core_cfg: CoreConfig, cfg_dir: str) -> Tuple[bool, str, str]:
|
def get_allnet_info(
|
||||||
|
cls, game_code: str, core_cfg: CoreConfig, cfg_dir: str
|
||||||
|
) -> Tuple[bool, str, str]:
|
||||||
game_cfg = CardMakerConfig()
|
game_cfg = CardMakerConfig()
|
||||||
if path.exists(f"{cfg_dir}/{CardMakerConstants.CONFIG_NAME}"):
|
if path.exists(f"{cfg_dir}/{CardMakerConstants.CONFIG_NAME}"):
|
||||||
game_cfg.update(yaml.safe_load(open(f"{cfg_dir}/{CardMakerConstants.CONFIG_NAME}")))
|
game_cfg.update(
|
||||||
|
yaml.safe_load(open(f"{cfg_dir}/{CardMakerConstants.CONFIG_NAME}"))
|
||||||
|
)
|
||||||
|
|
||||||
if not game_cfg.server.enable:
|
if not game_cfg.server.enable:
|
||||||
return (False, "", "")
|
return (False, "", "")
|
||||||
|
|
||||||
if core_cfg.server.is_develop:
|
if core_cfg.server.is_develop:
|
||||||
return (True, f"http://{core_cfg.title.hostname}:{core_cfg.title.port}/{game_code}/$v/", "")
|
return (
|
||||||
|
True,
|
||||||
|
f"http://{core_cfg.title.hostname}:{core_cfg.title.port}/{game_code}/$v/",
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
|
||||||
return (True, f"http://{core_cfg.title.hostname}/{game_code}/$v/", "")
|
return (True, f"http://{core_cfg.title.hostname}/{game_code}/$v/", "")
|
||||||
|
|
||||||
@@ -86,8 +101,9 @@ class CardMakerServlet():
|
|||||||
|
|
||||||
except zlib.error as e:
|
except zlib.error as e:
|
||||||
self.logger.error(
|
self.logger.error(
|
||||||
f"Failed to decompress v{version} {endpoint} request -> {e}")
|
f"Failed to decompress v{version} {endpoint} request -> {e}"
|
||||||
return zlib.compress("{\"stat\": \"0\"}".encode("utf-8"))
|
)
|
||||||
|
return zlib.compress(b'{"stat": "0"}')
|
||||||
|
|
||||||
req_data = json.loads(unzip)
|
req_data = json.loads(unzip)
|
||||||
|
|
||||||
@@ -95,22 +111,20 @@ class CardMakerServlet():
|
|||||||
|
|
||||||
func_to_find = "handle_" + inflection.underscore(endpoint) + "_request"
|
func_to_find = "handle_" + inflection.underscore(endpoint) + "_request"
|
||||||
|
|
||||||
|
if not hasattr(self.versions[internal_ver], func_to_find):
|
||||||
|
self.logger.warning(f"Unhandled v{version} request {endpoint}")
|
||||||
|
return zlib.compress(b'{"returnCode": 1}')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
handler = getattr(self.versions[internal_ver], func_to_find)
|
handler = getattr(self.versions[internal_ver], func_to_find)
|
||||||
resp = handler(req_data)
|
resp = handler(req_data)
|
||||||
|
|
||||||
except AttributeError as e:
|
|
||||||
self.logger.warning(
|
|
||||||
f"Unhandled v{version} request {endpoint} - {e}")
|
|
||||||
return zlib.compress("{\"stat\": \"0\"}".encode("utf-8"))
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(
|
self.logger.error(f"Error handling v{version} method {endpoint} - {e}")
|
||||||
f"Error handling v{version} method {endpoint} - {e}")
|
return zlib.compress(b'{"stat": "0"}')
|
||||||
return zlib.compress("{\"stat\": \"0\"}".encode("utf-8"))
|
|
||||||
|
|
||||||
if resp is None:
|
if resp is None:
|
||||||
resp = {'returnCode': 1}
|
resp = {"returnCode": 1}
|
||||||
|
|
||||||
self.logger.info(f"Response {resp}")
|
self.logger.info(f"Response {resp}")
|
||||||
|
|
||||||
|
|||||||
+32
-15
@@ -15,14 +15,21 @@ from titles.ongeki.config import OngekiConfig
|
|||||||
|
|
||||||
|
|
||||||
class CardMakerReader(BaseReader):
|
class CardMakerReader(BaseReader):
|
||||||
def __init__(self, config: CoreConfig, version: int, bin_dir: Optional[str],
|
def __init__(
|
||||||
opt_dir: Optional[str], extra: Optional[str]) -> None:
|
self,
|
||||||
|
config: CoreConfig,
|
||||||
|
version: int,
|
||||||
|
bin_dir: Optional[str],
|
||||||
|
opt_dir: Optional[str],
|
||||||
|
extra: Optional[str],
|
||||||
|
) -> None:
|
||||||
super().__init__(config, version, bin_dir, opt_dir, extra)
|
super().__init__(config, version, bin_dir, opt_dir, extra)
|
||||||
self.ongeki_data = OngekiData(config)
|
self.ongeki_data = OngekiData(config)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.logger.info(
|
self.logger.info(
|
||||||
f"Start importer for {CardMakerConstants.game_ver_to_string(version)}")
|
f"Start importer for {CardMakerConstants.game_ver_to_string(version)}"
|
||||||
|
)
|
||||||
except IndexError:
|
except IndexError:
|
||||||
self.logger.error(f"Invalid Card Maker version {version}")
|
self.logger.error(f"Invalid Card Maker version {version}")
|
||||||
exit(1)
|
exit(1)
|
||||||
@@ -30,7 +37,7 @@ class CardMakerReader(BaseReader):
|
|||||||
def read(self) -> None:
|
def read(self) -> None:
|
||||||
static_datas = {
|
static_datas = {
|
||||||
"static_gachas.csv": "read_ongeki_gacha_csv",
|
"static_gachas.csv": "read_ongeki_gacha_csv",
|
||||||
"static_gacha_cards.csv": "read_ongeki_gacha_card_csv"
|
"static_gacha_cards.csv": "read_ongeki_gacha_card_csv",
|
||||||
}
|
}
|
||||||
|
|
||||||
data_dirs = []
|
data_dirs = []
|
||||||
@@ -41,7 +48,9 @@ class CardMakerReader(BaseReader):
|
|||||||
read_csv = getattr(CardMakerReader, func)
|
read_csv = getattr(CardMakerReader, func)
|
||||||
read_csv(self, f"{self.bin_dir}/MU3/{file}")
|
read_csv(self, f"{self.bin_dir}/MU3/{file}")
|
||||||
else:
|
else:
|
||||||
self.logger.warn(f"Couldn't find {file} file in {self.bin_dir}, skipping")
|
self.logger.warn(
|
||||||
|
f"Couldn't find {file} file in {self.bin_dir}, skipping"
|
||||||
|
)
|
||||||
|
|
||||||
if self.opt_dir is not None:
|
if self.opt_dir is not None:
|
||||||
data_dirs += self.get_data_directories(self.opt_dir)
|
data_dirs += self.get_data_directories(self.opt_dir)
|
||||||
@@ -64,7 +73,7 @@ class CardMakerReader(BaseReader):
|
|||||||
row["kind"],
|
row["kind"],
|
||||||
type=row["type"],
|
type=row["type"],
|
||||||
isCeiling=True if row["isCeiling"] == "1" else False,
|
isCeiling=True if row["isCeiling"] == "1" else False,
|
||||||
maxSelectPoint=row["maxSelectPoint"]
|
maxSelectPoint=row["maxSelectPoint"],
|
||||||
)
|
)
|
||||||
|
|
||||||
self.logger.info(f"Added gacha {row['gachaId']}")
|
self.logger.info(f"Added gacha {row['gachaId']}")
|
||||||
@@ -81,7 +90,7 @@ class CardMakerReader(BaseReader):
|
|||||||
rarity=row["rarity"],
|
rarity=row["rarity"],
|
||||||
weight=row["weight"],
|
weight=row["weight"],
|
||||||
isPickup=True if row["isPickup"] == "1" else False,
|
isPickup=True if row["isPickup"] == "1" else False,
|
||||||
isSelect=True if row["isSelect"] == "1" else False
|
isSelect=True if row["isSelect"] == "1" else False,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.logger.info(f"Added card {row['cardId']} to gacha")
|
self.logger.info(f"Added card {row['cardId']} to gacha")
|
||||||
@@ -95,7 +104,7 @@ class CardMakerReader(BaseReader):
|
|||||||
"Pickup": "Pickup",
|
"Pickup": "Pickup",
|
||||||
"RecoverFiveShotFlag": "BonusRestored",
|
"RecoverFiveShotFlag": "BonusRestored",
|
||||||
"Free": "Free",
|
"Free": "Free",
|
||||||
"FreeSR": "Free"
|
"FreeSR": "Free",
|
||||||
}
|
}
|
||||||
|
|
||||||
for root, dirs, files in os.walk(base_dir):
|
for root, dirs, files in os.walk(base_dir):
|
||||||
@@ -104,13 +113,19 @@ class CardMakerReader(BaseReader):
|
|||||||
with open(f"{root}/{dir}/Gacha.xml", "r", encoding="utf-8") as f:
|
with open(f"{root}/{dir}/Gacha.xml", "r", encoding="utf-8") as f:
|
||||||
troot = ET.fromstring(f.read())
|
troot = ET.fromstring(f.read())
|
||||||
|
|
||||||
name = troot.find('Name').find('str').text
|
name = troot.find("Name").find("str").text
|
||||||
gacha_id = int(troot.find('Name').find('id').text)
|
gacha_id = int(troot.find("Name").find("id").text)
|
||||||
|
|
||||||
# skip already existing gachas
|
# skip already existing gachas
|
||||||
if self.ongeki_data.static.get_gacha(
|
if (
|
||||||
OngekiConstants.VER_ONGEKI_BRIGHT_MEMORY, gacha_id) is not None:
|
self.ongeki_data.static.get_gacha(
|
||||||
self.logger.info(f"Gacha {gacha_id} already added, skipping")
|
OngekiConstants.VER_ONGEKI_BRIGHT_MEMORY, gacha_id
|
||||||
|
)
|
||||||
|
is not None
|
||||||
|
):
|
||||||
|
self.logger.info(
|
||||||
|
f"Gacha {gacha_id} already added, skipping"
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 1140 is the first bright memory gacha
|
# 1140 is the first bright memory gacha
|
||||||
@@ -120,7 +135,8 @@ class CardMakerReader(BaseReader):
|
|||||||
version = OngekiConstants.VER_ONGEKI_BRIGHT_MEMORY
|
version = OngekiConstants.VER_ONGEKI_BRIGHT_MEMORY
|
||||||
|
|
||||||
gacha_kind = OngekiConstants.CM_GACHA_KINDS[
|
gacha_kind = OngekiConstants.CM_GACHA_KINDS[
|
||||||
type_to_kind[troot.find('Type').text]].value
|
type_to_kind[troot.find("Type").text]
|
||||||
|
].value
|
||||||
|
|
||||||
# hardcode which gachas get "Select Gacha" with 33 points
|
# hardcode which gachas get "Select Gacha" with 33 points
|
||||||
is_ceiling, max_select_point = 0, 0
|
is_ceiling, max_select_point = 0, 0
|
||||||
@@ -134,5 +150,6 @@ class CardMakerReader(BaseReader):
|
|||||||
name,
|
name,
|
||||||
gacha_kind,
|
gacha_kind,
|
||||||
isCeiling=is_ceiling,
|
isCeiling=is_ceiling,
|
||||||
maxSelectPoint=max_select_point)
|
maxSelectPoint=max_select_point,
|
||||||
|
)
|
||||||
self.logger.info(f"Added gacha {gacha_id}")
|
self.logger.info(f"Added gacha {gacha_id}")
|
||||||
|
|||||||
+332
-211
@@ -11,7 +11,8 @@ from titles.cxb.config import CxbConfig
|
|||||||
from titles.cxb.const import CxbConstants
|
from titles.cxb.const import CxbConstants
|
||||||
from titles.cxb.database import CxbData
|
from titles.cxb.database import CxbData
|
||||||
|
|
||||||
class CxbBase():
|
|
||||||
|
class CxbBase:
|
||||||
def __init__(self, cfg: CoreConfig, game_cfg: CxbConfig) -> None:
|
def __init__(self, cfg: CoreConfig, game_cfg: CxbConfig) -> None:
|
||||||
self.config = cfg # Config file
|
self.config = cfg # Config file
|
||||||
self.game_config = game_cfg
|
self.game_config = game_cfg
|
||||||
@@ -21,38 +22,42 @@ class CxbBase():
|
|||||||
self.version = CxbConstants.VER_CROSSBEATS_REV
|
self.version = CxbConstants.VER_CROSSBEATS_REV
|
||||||
|
|
||||||
def handle_action_rpreq_request(self, data: Dict) -> Dict:
|
def handle_action_rpreq_request(self, data: Dict) -> Dict:
|
||||||
return({})
|
return {}
|
||||||
|
|
||||||
def handle_action_hitreq_request(self, data: Dict) -> Dict:
|
def handle_action_hitreq_request(self, data: Dict) -> Dict:
|
||||||
return({"data":[]})
|
return {"data": []}
|
||||||
|
|
||||||
def handle_auth_usercheck_request(self, data: Dict) -> Dict:
|
def handle_auth_usercheck_request(self, data: Dict) -> Dict:
|
||||||
profile = self.data.profile.get_profile_index(0, data["usercheck"]["authid"], self.version)
|
profile = self.data.profile.get_profile_index(
|
||||||
|
0, data["usercheck"]["authid"], self.version
|
||||||
|
)
|
||||||
if profile is not None:
|
if profile is not None:
|
||||||
self.logger.info(f"User {data['usercheck']['authid']} has CXB profile")
|
self.logger.info(f"User {data['usercheck']['authid']} has CXB profile")
|
||||||
return({"exist": "true", "logout": "true"})
|
return {"exist": "true", "logout": "true"}
|
||||||
|
|
||||||
self.logger.info(f"No profile for aime id {data['usercheck']['authid']}")
|
self.logger.info(f"No profile for aime id {data['usercheck']['authid']}")
|
||||||
return({"exist": "false", "logout": "true"})
|
return {"exist": "false", "logout": "true"}
|
||||||
|
|
||||||
def handle_auth_entry_request(self, data: Dict) -> Dict:
|
def handle_auth_entry_request(self, data: Dict) -> Dict:
|
||||||
self.logger.info(f"New profile for {data['entry']['authid']}")
|
self.logger.info(f"New profile for {data['entry']['authid']}")
|
||||||
return({"token": data["entry"]["authid"], "uid": data["entry"]["authid"]})
|
return {"token": data["entry"]["authid"], "uid": data["entry"]["authid"]}
|
||||||
|
|
||||||
def handle_auth_login_request(self, data: Dict) -> Dict:
|
def handle_auth_login_request(self, data: Dict) -> Dict:
|
||||||
profile = self.data.profile.get_profile_index(0, data["login"]["authid"], self.version)
|
profile = self.data.profile.get_profile_index(
|
||||||
|
0, data["login"]["authid"], self.version
|
||||||
|
)
|
||||||
|
|
||||||
if profile is not None:
|
if profile is not None:
|
||||||
self.logger.info(f"Login user {data['login']['authid']}")
|
self.logger.info(f"Login user {data['login']['authid']}")
|
||||||
return({"token": data["login"]["authid"], "uid": data["login"]["authid"]})
|
return {"token": data["login"]["authid"], "uid": data["login"]["authid"]}
|
||||||
|
|
||||||
self.logger.warn(f"User {data['login']['authid']} does not have a profile")
|
self.logger.warn(f"User {data['login']['authid']} does not have a profile")
|
||||||
return({})
|
return {}
|
||||||
|
|
||||||
def handle_action_loadrange_request(self, data: Dict) -> Dict:
|
def handle_action_loadrange_request(self, data: Dict) -> Dict:
|
||||||
range_start = data['loadrange']['range'][0]
|
range_start = data["loadrange"]["range"][0]
|
||||||
range_end = data['loadrange']['range'][1]
|
range_end = data["loadrange"]["range"][1]
|
||||||
uid = data['loadrange']['uid']
|
uid = data["loadrange"]["uid"]
|
||||||
|
|
||||||
self.logger.info(f"Load data for {uid}")
|
self.logger.info(f"Load data for {uid}")
|
||||||
profile = self.data.profile.get_profile(uid, self.version)
|
profile = self.data.profile.get_profile(uid, self.version)
|
||||||
@@ -66,27 +71,31 @@ class CxbBase():
|
|||||||
profile_data = profile_index["data"]
|
profile_data = profile_index["data"]
|
||||||
|
|
||||||
if int(range_start) == 800000:
|
if int(range_start) == 800000:
|
||||||
return({"index":range_start, "data":[], "version":10400})
|
return {"index": range_start, "data": [], "version": 10400}
|
||||||
|
|
||||||
if not ( int(range_start) <= int(profile_index[3]) <= int(range_end) ):
|
if not (int(range_start) <= int(profile_index[3]) <= int(range_end)):
|
||||||
continue
|
continue
|
||||||
#Prevent loading of the coupons within the profile to use the force unlock instead
|
# Prevent loading of the coupons within the profile to use the force unlock instead
|
||||||
elif 500 <= int(profile_index[3]) <= 510:
|
elif 500 <= int(profile_index[3]) <= 510:
|
||||||
continue
|
continue
|
||||||
#Prevent loading of songs saved in the profile
|
# Prevent loading of songs saved in the profile
|
||||||
elif 100000 <= int(profile_index[3]) <= 110000:
|
elif 100000 <= int(profile_index[3]) <= 110000:
|
||||||
continue
|
continue
|
||||||
#Prevent loading of the shop list / unlocked titles & icons saved in the profile
|
# Prevent loading of the shop list / unlocked titles & icons saved in the profile
|
||||||
elif 200000 <= int(profile_index[3]) <= 210000:
|
elif 200000 <= int(profile_index[3]) <= 210000:
|
||||||
continue
|
continue
|
||||||
#Prevent loading of stories in the profile
|
# Prevent loading of stories in the profile
|
||||||
elif 900000 <= int(profile_index[3]) <= 900200:
|
elif 900000 <= int(profile_index[3]) <= 900200:
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
index.append(profile_index[3])
|
index.append(profile_index[3])
|
||||||
data1.append(b64encode(bytes(json.dumps(profile_data, separators=(',', ':')), 'utf-8')).decode('utf-8'))
|
data1.append(
|
||||||
|
b64encode(
|
||||||
|
bytes(json.dumps(profile_data, separators=(",", ":")), "utf-8")
|
||||||
|
).decode("utf-8")
|
||||||
|
)
|
||||||
|
|
||||||
'''
|
"""
|
||||||
100000 = Songs
|
100000 = Songs
|
||||||
200000 = Shop
|
200000 = Shop
|
||||||
300000 = Courses
|
300000 = Courses
|
||||||
@@ -96,101 +105,140 @@ class CxbBase():
|
|||||||
700000 = rcLog
|
700000 = rcLog
|
||||||
800000 = Partners
|
800000 = Partners
|
||||||
900000 = Stories
|
900000 = Stories
|
||||||
'''
|
"""
|
||||||
|
|
||||||
# Coupons
|
# Coupons
|
||||||
for i in range(500,510):
|
for i in range(500, 510):
|
||||||
index.append(str(i))
|
index.append(str(i))
|
||||||
couponid = int(i) - 500
|
couponid = int(i) - 500
|
||||||
dataValue = [{
|
dataValue = [
|
||||||
"couponId":str(couponid),
|
{
|
||||||
"couponNum":"1",
|
"couponId": str(couponid),
|
||||||
"couponLog":[],
|
"couponNum": "1",
|
||||||
}]
|
"couponLog": [],
|
||||||
data1.append(b64encode(bytes(json.dumps(dataValue[0], separators=(',', ':')), 'utf-8')).decode('utf-8'))
|
}
|
||||||
|
]
|
||||||
|
data1.append(
|
||||||
|
b64encode(
|
||||||
|
bytes(json.dumps(dataValue[0], separators=(",", ":")), "utf-8")
|
||||||
|
).decode("utf-8")
|
||||||
|
)
|
||||||
|
|
||||||
# ShopList_Title
|
# ShopList_Title
|
||||||
for i in range(200000,201451):
|
for i in range(200000, 201451):
|
||||||
index.append(str(i))
|
index.append(str(i))
|
||||||
shopid = int(i) - 200000
|
shopid = int(i) - 200000
|
||||||
dataValue = [{
|
dataValue = [
|
||||||
"shopId":shopid,
|
{
|
||||||
"shopState":"2",
|
"shopId": shopid,
|
||||||
"isDisable":"t",
|
"shopState": "2",
|
||||||
"isDeleted":"f",
|
"isDisable": "t",
|
||||||
"isSpecialFlag":"f"
|
"isDeleted": "f",
|
||||||
}]
|
"isSpecialFlag": "f",
|
||||||
data1.append(b64encode(bytes(json.dumps(dataValue[0], separators=(',', ':')), 'utf-8')).decode('utf-8'))
|
}
|
||||||
|
]
|
||||||
|
data1.append(
|
||||||
|
b64encode(
|
||||||
|
bytes(json.dumps(dataValue[0], separators=(",", ":")), "utf-8")
|
||||||
|
).decode("utf-8")
|
||||||
|
)
|
||||||
|
|
||||||
#ShopList_Icon
|
# ShopList_Icon
|
||||||
for i in range(202000,202264):
|
for i in range(202000, 202264):
|
||||||
index.append(str(i))
|
index.append(str(i))
|
||||||
shopid = int(i) - 200000
|
shopid = int(i) - 200000
|
||||||
dataValue = [{
|
dataValue = [
|
||||||
"shopId":shopid,
|
{
|
||||||
"shopState":"2",
|
"shopId": shopid,
|
||||||
"isDisable":"t",
|
"shopState": "2",
|
||||||
"isDeleted":"f",
|
"isDisable": "t",
|
||||||
"isSpecialFlag":"f"
|
"isDeleted": "f",
|
||||||
}]
|
"isSpecialFlag": "f",
|
||||||
data1.append(b64encode(bytes(json.dumps(dataValue[0], separators=(',', ':')), 'utf-8')).decode('utf-8'))
|
}
|
||||||
|
]
|
||||||
|
data1.append(
|
||||||
|
b64encode(
|
||||||
|
bytes(json.dumps(dataValue[0], separators=(",", ":")), "utf-8")
|
||||||
|
).decode("utf-8")
|
||||||
|
)
|
||||||
|
|
||||||
#Stories
|
# Stories
|
||||||
for i in range(900000,900003):
|
for i in range(900000, 900003):
|
||||||
index.append(str(i))
|
index.append(str(i))
|
||||||
storyid = int(i) - 900000
|
storyid = int(i) - 900000
|
||||||
dataValue = [{
|
dataValue = [
|
||||||
"storyId":storyid,
|
{
|
||||||
"unlockState1":["t"] * 10,
|
"storyId": storyid,
|
||||||
"unlockState2":["t"] * 10,
|
"unlockState1": ["t"] * 10,
|
||||||
"unlockState3":["t"] * 10,
|
"unlockState2": ["t"] * 10,
|
||||||
"unlockState4":["t"] * 10,
|
"unlockState3": ["t"] * 10,
|
||||||
"unlockState5":["t"] * 10,
|
"unlockState4": ["t"] * 10,
|
||||||
"unlockState6":["t"] * 10,
|
"unlockState5": ["t"] * 10,
|
||||||
"unlockState7":["t"] * 10,
|
"unlockState6": ["t"] * 10,
|
||||||
"unlockState8":["t"] * 10,
|
"unlockState7": ["t"] * 10,
|
||||||
"unlockState9":["t"] * 10,
|
"unlockState8": ["t"] * 10,
|
||||||
"unlockState10":["t"] * 10,
|
"unlockState9": ["t"] * 10,
|
||||||
"unlockState11":["t"] * 10,
|
"unlockState10": ["t"] * 10,
|
||||||
"unlockState12":["t"] * 10,
|
"unlockState11": ["t"] * 10,
|
||||||
"unlockState13":["t"] * 10,
|
"unlockState12": ["t"] * 10,
|
||||||
"unlockState14":["t"] * 10,
|
"unlockState13": ["t"] * 10,
|
||||||
"unlockState15":["t"] * 10,
|
"unlockState14": ["t"] * 10,
|
||||||
"unlockState16":["t"] * 10
|
"unlockState15": ["t"] * 10,
|
||||||
}]
|
"unlockState16": ["t"] * 10,
|
||||||
data1.append(b64encode(bytes(json.dumps(dataValue[0], separators=(',', ':')), 'utf-8')).decode('utf-8'))
|
}
|
||||||
|
]
|
||||||
|
data1.append(
|
||||||
|
b64encode(
|
||||||
|
bytes(json.dumps(dataValue[0], separators=(",", ":")), "utf-8")
|
||||||
|
).decode("utf-8")
|
||||||
|
)
|
||||||
|
|
||||||
for song in songs:
|
for song in songs:
|
||||||
song_data = song["data"]
|
song_data = song["data"]
|
||||||
songCode = []
|
songCode = []
|
||||||
|
|
||||||
songCode.append({
|
songCode.append(
|
||||||
"mcode": song_data['mcode'],
|
{
|
||||||
"musicState": song_data['musicState'],
|
"mcode": song_data["mcode"],
|
||||||
"playCount": song_data['playCount'],
|
"musicState": song_data["musicState"],
|
||||||
"totalScore": song_data['totalScore'],
|
"playCount": song_data["playCount"],
|
||||||
"highScore": song_data['highScore'],
|
"totalScore": song_data["totalScore"],
|
||||||
"everHighScore": song_data['everHighScore'] if 'everHighScore' in song_data else ["0","0","0","0","0"],
|
"highScore": song_data["highScore"],
|
||||||
"clearRate": song_data['clearRate'],
|
"everHighScore": song_data["everHighScore"]
|
||||||
"rankPoint": song_data['rankPoint'],
|
if "everHighScore" in song_data
|
||||||
"normalCR": song_data['normalCR'] if 'normalCR' in song_data else ["0","0","0","0","0"],
|
else ["0", "0", "0", "0", "0"],
|
||||||
"survivalCR": song_data['survivalCR'] if 'survivalCR' in song_data else ["0","0","0","0","0"],
|
"clearRate": song_data["clearRate"],
|
||||||
"ultimateCR": song_data['ultimateCR'] if 'ultimateCR' in song_data else ["0","0","0","0","0"],
|
"rankPoint": song_data["rankPoint"],
|
||||||
"nohopeCR": song_data['nohopeCR'] if 'nohopeCR' in song_data else ["0","0","0","0","0"],
|
"normalCR": song_data["normalCR"]
|
||||||
"combo": song_data['combo'],
|
if "normalCR" in song_data
|
||||||
"coupleUserId": song_data['coupleUserId'],
|
else ["0", "0", "0", "0", "0"],
|
||||||
"difficulty": song_data['difficulty'],
|
"survivalCR": song_data["survivalCR"]
|
||||||
"isFullCombo": song_data['isFullCombo'],
|
if "survivalCR" in song_data
|
||||||
"clearGaugeType": song_data['clearGaugeType'],
|
else ["0", "0", "0", "0", "0"],
|
||||||
"fieldType": song_data['fieldType'],
|
"ultimateCR": song_data["ultimateCR"]
|
||||||
"gameType": song_data['gameType'],
|
if "ultimateCR" in song_data
|
||||||
"grade": song_data['grade'],
|
else ["0", "0", "0", "0", "0"],
|
||||||
"unlockState": song_data['unlockState'],
|
"nohopeCR": song_data["nohopeCR"]
|
||||||
"extraState": song_data['extraState']
|
if "nohopeCR" in song_data
|
||||||
})
|
else ["0", "0", "0", "0", "0"],
|
||||||
index.append(song_data['index'])
|
"combo": song_data["combo"],
|
||||||
data1.append(b64encode(bytes(json.dumps(songCode[0], separators=(',', ':')), 'utf-8')).decode('utf-8'))
|
"coupleUserId": song_data["coupleUserId"],
|
||||||
|
"difficulty": song_data["difficulty"],
|
||||||
|
"isFullCombo": song_data["isFullCombo"],
|
||||||
|
"clearGaugeType": song_data["clearGaugeType"],
|
||||||
|
"fieldType": song_data["fieldType"],
|
||||||
|
"gameType": song_data["gameType"],
|
||||||
|
"grade": song_data["grade"],
|
||||||
|
"unlockState": song_data["unlockState"],
|
||||||
|
"extraState": song_data["extraState"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
index.append(song_data["index"])
|
||||||
|
data1.append(
|
||||||
|
b64encode(
|
||||||
|
bytes(json.dumps(songCode[0], separators=(",", ":")), "utf-8")
|
||||||
|
).decode("utf-8")
|
||||||
|
)
|
||||||
|
|
||||||
for v in index:
|
for v in index:
|
||||||
try:
|
try:
|
||||||
@@ -198,66 +246,81 @@ class CxbBase():
|
|||||||
v_profile_data = v_profile["data"]
|
v_profile_data = v_profile["data"]
|
||||||
versionindex.append(int(v_profile_data["appVersion"]))
|
versionindex.append(int(v_profile_data["appVersion"]))
|
||||||
except:
|
except:
|
||||||
versionindex.append('10400')
|
versionindex.append("10400")
|
||||||
|
|
||||||
return({"index":index, "data":data1, "version":versionindex})
|
return {"index": index, "data": data1, "version": versionindex}
|
||||||
|
|
||||||
def handle_action_saveindex_request(self, data: Dict) -> Dict:
|
def handle_action_saveindex_request(self, data: Dict) -> Dict:
|
||||||
save_data = data['saveindex']
|
save_data = data["saveindex"]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
#REV Omnimix Version Fetcher
|
# REV Omnimix Version Fetcher
|
||||||
gameversion = data['saveindex']['data'][0][2]
|
gameversion = data["saveindex"]["data"][0][2]
|
||||||
self.logger.warning(f"Game Version is {gameversion}")
|
self.logger.warning(f"Game Version is {gameversion}")
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if "10205" in gameversion:
|
if "10205" in gameversion:
|
||||||
self.logger.info(f"Saving CrossBeats REV profile for {data['saveindex']['uid']}")
|
self.logger.info(
|
||||||
#Alright.... time to bring the jank code
|
f"Saving CrossBeats REV profile for {data['saveindex']['uid']}"
|
||||||
|
)
|
||||||
|
# Alright.... time to bring the jank code
|
||||||
|
|
||||||
for value in data['saveindex']['data']:
|
for value in data["saveindex"]["data"]:
|
||||||
|
if "playedUserId" in value[1]:
|
||||||
if 'playedUserId' in value[1]:
|
self.data.profile.put_profile(
|
||||||
self.data.profile.put_profile(data['saveindex']['uid'], self.version, value[0], value[1])
|
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 "mcode" not in value[1]:
|
||||||
if 'shopId' in value:
|
self.data.profile.put_profile(
|
||||||
|
data["saveindex"]["uid"], self.version, value[0], value[1]
|
||||||
|
)
|
||||||
|
if "shopId" in value:
|
||||||
continue
|
continue
|
||||||
if 'mcode' in value[1] and 'musicState' in value[1]:
|
if "mcode" in value[1] and "musicState" in value[1]:
|
||||||
song_json = json.loads(value[1])
|
song_json = json.loads(value[1])
|
||||||
|
|
||||||
songCode = []
|
songCode = []
|
||||||
songCode.append({
|
songCode.append(
|
||||||
"mcode": song_json['mcode'],
|
{
|
||||||
"musicState": song_json['musicState'],
|
"mcode": song_json["mcode"],
|
||||||
"playCount": song_json['playCount'],
|
"musicState": song_json["musicState"],
|
||||||
"totalScore": song_json['totalScore'],
|
"playCount": song_json["playCount"],
|
||||||
"highScore": song_json['highScore'],
|
"totalScore": song_json["totalScore"],
|
||||||
"clearRate": song_json['clearRate'],
|
"highScore": song_json["highScore"],
|
||||||
"rankPoint": song_json['rankPoint'],
|
"clearRate": song_json["clearRate"],
|
||||||
"combo": song_json['combo'],
|
"rankPoint": song_json["rankPoint"],
|
||||||
"coupleUserId": song_json['coupleUserId'],
|
"combo": song_json["combo"],
|
||||||
"difficulty": song_json['difficulty'],
|
"coupleUserId": song_json["coupleUserId"],
|
||||||
"isFullCombo": song_json['isFullCombo'],
|
"difficulty": song_json["difficulty"],
|
||||||
"clearGaugeType": song_json['clearGaugeType'],
|
"isFullCombo": song_json["isFullCombo"],
|
||||||
"fieldType": song_json['fieldType'],
|
"clearGaugeType": song_json["clearGaugeType"],
|
||||||
"gameType": song_json['gameType'],
|
"fieldType": song_json["fieldType"],
|
||||||
"grade": song_json['grade'],
|
"gameType": song_json["gameType"],
|
||||||
"unlockState": song_json['unlockState'],
|
"grade": song_json["grade"],
|
||||||
"extraState": song_json['extraState'],
|
"unlockState": song_json["unlockState"],
|
||||||
"index": value[0]
|
"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({})
|
)
|
||||||
|
self.data.score.put_best_score(
|
||||||
|
data["saveindex"]["uid"],
|
||||||
|
song_json["mcode"],
|
||||||
|
self.version,
|
||||||
|
value[0],
|
||||||
|
songCode[0],
|
||||||
|
)
|
||||||
|
return {}
|
||||||
else:
|
else:
|
||||||
self.logger.info(f"Saving CrossBeats REV Sunrise profile for {data['saveindex']['uid']}")
|
self.logger.info(
|
||||||
|
f"Saving CrossBeats REV Sunrise profile for {data['saveindex']['uid']}"
|
||||||
|
)
|
||||||
|
|
||||||
#Sunrise
|
# Sunrise
|
||||||
try:
|
try:
|
||||||
profileIndex = save_data['index'].index('0')
|
profileIndex = save_data["index"].index("0")
|
||||||
except:
|
except:
|
||||||
return({"data":""}) #Maybe
|
return {"data": ""} # Maybe
|
||||||
|
|
||||||
profile = json.loads(save_data["data"][profileIndex])
|
profile = json.loads(save_data["data"][profileIndex])
|
||||||
aimeId = profile["aimeId"]
|
aimeId = profile["aimeId"]
|
||||||
@@ -265,65 +328,91 @@ class CxbBase():
|
|||||||
|
|
||||||
for index, value in enumerate(data["saveindex"]["data"]):
|
for index, value in enumerate(data["saveindex"]["data"]):
|
||||||
if int(data["saveindex"]["index"][index]) == 101:
|
if int(data["saveindex"]["index"][index]) == 101:
|
||||||
self.data.profile.put_profile(aimeId, self.version, data["saveindex"]["index"][index], value)
|
self.data.profile.put_profile(
|
||||||
if int(data["saveindex"]["index"][index]) >= 700000 and int(data["saveindex"]["index"][index])<= 701000:
|
aimeId, self.version, data["saveindex"]["index"][index], value
|
||||||
self.data.profile.put_profile(aimeId, self.version, data["saveindex"]["index"][index], value)
|
)
|
||||||
if int(data["saveindex"]["index"][index]) >= 500 and int(data["saveindex"]["index"][index]) <= 510:
|
if (
|
||||||
self.data.profile.put_profile(aimeId, self.version, data["saveindex"]["index"][index], value)
|
int(data["saveindex"]["index"][index]) >= 700000
|
||||||
if 'playedUserId' in value:
|
and int(data["saveindex"]["index"][index]) <= 701000
|
||||||
self.data.profile.put_profile(aimeId, self.version, data["saveindex"]["index"][index], json.loads(value))
|
):
|
||||||
if 'mcode' not in value and "normalCR" not in value:
|
self.data.profile.put_profile(
|
||||||
self.data.profile.put_profile(aimeId, self.version, data["saveindex"]["index"][index], json.loads(value))
|
aimeId, self.version, data["saveindex"]["index"][index], value
|
||||||
if 'shopId' in value:
|
)
|
||||||
|
if (
|
||||||
|
int(data["saveindex"]["index"][index]) >= 500
|
||||||
|
and int(data["saveindex"]["index"][index]) <= 510
|
||||||
|
):
|
||||||
|
self.data.profile.put_profile(
|
||||||
|
aimeId, self.version, data["saveindex"]["index"][index], value
|
||||||
|
)
|
||||||
|
if "playedUserId" in value:
|
||||||
|
self.data.profile.put_profile(
|
||||||
|
aimeId,
|
||||||
|
self.version,
|
||||||
|
data["saveindex"]["index"][index],
|
||||||
|
json.loads(value),
|
||||||
|
)
|
||||||
|
if "mcode" not in value and "normalCR" not in value:
|
||||||
|
self.data.profile.put_profile(
|
||||||
|
aimeId,
|
||||||
|
self.version,
|
||||||
|
data["saveindex"]["index"][index],
|
||||||
|
json.loads(value),
|
||||||
|
)
|
||||||
|
if "shopId" in value:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# MusicList Index for the profile
|
# MusicList Index for the profile
|
||||||
indexSongList = []
|
indexSongList = []
|
||||||
for value in data["saveindex"]["index"]:
|
for value in data["saveindex"]["index"]:
|
||||||
if int(value) in range(100000,110000):
|
if int(value) in range(100000, 110000):
|
||||||
indexSongList.append(value)
|
indexSongList.append(value)
|
||||||
|
|
||||||
for index, value in enumerate(data["saveindex"]["data"]):
|
for index, value in enumerate(data["saveindex"]["data"]):
|
||||||
if 'mcode' not in value:
|
if "mcode" not in value:
|
||||||
continue
|
continue
|
||||||
if 'playedUserId' in value:
|
if "playedUserId" in value:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
data1 = json.loads(value)
|
data1 = json.loads(value)
|
||||||
|
|
||||||
songCode = []
|
songCode = []
|
||||||
songCode.append({
|
songCode.append(
|
||||||
"mcode": data1['mcode'],
|
{
|
||||||
"musicState": data1['musicState'],
|
"mcode": data1["mcode"],
|
||||||
"playCount": data1['playCount'],
|
"musicState": data1["musicState"],
|
||||||
"totalScore": data1['totalScore'],
|
"playCount": data1["playCount"],
|
||||||
"highScore": data1['highScore'],
|
"totalScore": data1["totalScore"],
|
||||||
"everHighScore": data1['everHighScore'],
|
"highScore": data1["highScore"],
|
||||||
"clearRate": data1['clearRate'],
|
"everHighScore": data1["everHighScore"],
|
||||||
"rankPoint": data1['rankPoint'],
|
"clearRate": data1["clearRate"],
|
||||||
"normalCR": data1['normalCR'],
|
"rankPoint": data1["rankPoint"],
|
||||||
"survivalCR": data1['survivalCR'],
|
"normalCR": data1["normalCR"],
|
||||||
"ultimateCR": data1['ultimateCR'],
|
"survivalCR": data1["survivalCR"],
|
||||||
"nohopeCR": data1['nohopeCR'],
|
"ultimateCR": data1["ultimateCR"],
|
||||||
"combo": data1['combo'],
|
"nohopeCR": data1["nohopeCR"],
|
||||||
"coupleUserId": data1['coupleUserId'],
|
"combo": data1["combo"],
|
||||||
"difficulty": data1['difficulty'],
|
"coupleUserId": data1["coupleUserId"],
|
||||||
"isFullCombo": data1['isFullCombo'],
|
"difficulty": data1["difficulty"],
|
||||||
"clearGaugeType": data1['clearGaugeType'],
|
"isFullCombo": data1["isFullCombo"],
|
||||||
"fieldType": data1['fieldType'],
|
"clearGaugeType": data1["clearGaugeType"],
|
||||||
"gameType": data1['gameType'],
|
"fieldType": data1["fieldType"],
|
||||||
"grade": data1['grade'],
|
"gameType": data1["gameType"],
|
||||||
"unlockState": data1['unlockState'],
|
"grade": data1["grade"],
|
||||||
"extraState": data1['extraState'],
|
"unlockState": data1["unlockState"],
|
||||||
"index": indexSongList[i]
|
"extraState": data1["extraState"],
|
||||||
})
|
"index": indexSongList[i],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
self.data.score.put_best_score(aimeId, data1['mcode'], self.version, indexSongList[i], songCode[0])
|
self.data.score.put_best_score(
|
||||||
|
aimeId, data1["mcode"], self.version, indexSongList[i], songCode[0]
|
||||||
|
)
|
||||||
i += 1
|
i += 1
|
||||||
return({})
|
return {}
|
||||||
|
|
||||||
def handle_action_sprankreq_request(self, data: Dict) -> Dict:
|
def handle_action_sprankreq_request(self, data: Dict) -> Dict:
|
||||||
uid = data['sprankreq']['uid']
|
uid = data["sprankreq"]["uid"]
|
||||||
self.logger.info(f"Get best rankings for {uid}")
|
self.logger.info(f"Get best rankings for {uid}")
|
||||||
p = self.data.score.get_best_rankings(uid)
|
p = self.data.score.get_best_rankings(uid)
|
||||||
|
|
||||||
@@ -331,55 +420,83 @@ class CxbBase():
|
|||||||
|
|
||||||
for rank in p:
|
for rank in p:
|
||||||
if rank["song_id"] is not None:
|
if rank["song_id"] is not None:
|
||||||
rankList.append({
|
rankList.append(
|
||||||
"sc": [rank["score"],rank["song_id"]],
|
{
|
||||||
|
"sc": [rank["score"], rank["song_id"]],
|
||||||
"rid": rank["rev_id"],
|
"rid": rank["rev_id"],
|
||||||
"clear": rank["clear"]
|
"clear": rank["clear"],
|
||||||
})
|
}
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
rankList.append({
|
rankList.append(
|
||||||
|
{
|
||||||
"sc": [rank["score"]],
|
"sc": [rank["score"]],
|
||||||
"rid": rank["rev_id"],
|
"rid": rank["rev_id"],
|
||||||
"clear": rank["clear"]
|
"clear": rank["clear"],
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return({
|
return {
|
||||||
"uid": data["sprankreq"]["uid"],
|
"uid": data["sprankreq"]["uid"],
|
||||||
"aid": data["sprankreq"]["aid"],
|
"aid": data["sprankreq"]["aid"],
|
||||||
"rank": rankList,
|
"rank": rankList,
|
||||||
"rankx":[1,1,1]
|
"rankx": [1, 1, 1],
|
||||||
})
|
}
|
||||||
|
|
||||||
def handle_action_getadv_request(self, data: Dict) -> Dict:
|
def handle_action_getadv_request(self, data: Dict) -> Dict:
|
||||||
return({"data":[{"r":"1","i":"100300","c":"20"}]})
|
return {"data": [{"r": "1", "i": "100300", "c": "20"}]}
|
||||||
|
|
||||||
def handle_action_getmsg_request(self, data: Dict) -> Dict:
|
def handle_action_getmsg_request(self, data: Dict) -> Dict:
|
||||||
return({"msgs":[]})
|
return {"msgs": []}
|
||||||
|
|
||||||
def handle_auth_logout_request(self, data: Dict) -> Dict:
|
def handle_auth_logout_request(self, data: Dict) -> Dict:
|
||||||
return({"auth":True})
|
return {"auth": True}
|
||||||
|
|
||||||
def handle_action_rankreg_request(self, data: Dict) -> Dict:
|
def handle_action_rankreg_request(self, data: Dict) -> Dict:
|
||||||
uid = data['rankreg']['uid']
|
uid = data["rankreg"]["uid"]
|
||||||
self.logger.info(f"Put {len(data['rankreg']['data'])} rankings for {uid}")
|
self.logger.info(f"Put {len(data['rankreg']['data'])} rankings for {uid}")
|
||||||
|
|
||||||
for rid in data['rankreg']['data']:
|
for rid in data["rankreg"]["data"]:
|
||||||
#REV S2
|
# REV S2
|
||||||
if "clear" in rid:
|
if "clear" in rid:
|
||||||
try:
|
try:
|
||||||
self.data.score.put_ranking(user_id=uid, rev_id=int(rid["rid"]), song_id=int(rid["sc"][1]), score=int(rid["sc"][0]), clear=rid["clear"])
|
self.data.score.put_ranking(
|
||||||
|
user_id=uid,
|
||||||
|
rev_id=int(rid["rid"]),
|
||||||
|
song_id=int(rid["sc"][1]),
|
||||||
|
score=int(rid["sc"][0]),
|
||||||
|
clear=rid["clear"],
|
||||||
|
)
|
||||||
except:
|
except:
|
||||||
self.data.score.put_ranking(user_id=uid, rev_id=int(rid["rid"]), song_id=0, score=int(rid["sc"][0]), clear=rid["clear"])
|
self.data.score.put_ranking(
|
||||||
#REV
|
user_id=uid,
|
||||||
|
rev_id=int(rid["rid"]),
|
||||||
|
song_id=0,
|
||||||
|
score=int(rid["sc"][0]),
|
||||||
|
clear=rid["clear"],
|
||||||
|
)
|
||||||
|
# REV
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
self.data.score.put_ranking(user_id=uid, rev_id=int(rid["rid"]), song_id=int(rid["sc"][1]), score=int(rid["sc"][0]), clear=0)
|
self.data.score.put_ranking(
|
||||||
|
user_id=uid,
|
||||||
|
rev_id=int(rid["rid"]),
|
||||||
|
song_id=int(rid["sc"][1]),
|
||||||
|
score=int(rid["sc"][0]),
|
||||||
|
clear=0,
|
||||||
|
)
|
||||||
except:
|
except:
|
||||||
self.data.score.put_ranking(user_id=uid, rev_id=int(rid["rid"]), song_id=0, score=int(rid["sc"][0]), clear=0)
|
self.data.score.put_ranking(
|
||||||
return({})
|
user_id=uid,
|
||||||
|
rev_id=int(rid["rid"]),
|
||||||
|
song_id=0,
|
||||||
|
score=int(rid["sc"][0]),
|
||||||
|
clear=0,
|
||||||
|
)
|
||||||
|
return {}
|
||||||
|
|
||||||
def handle_action_addenergy_request(self, data: Dict) -> Dict:
|
def handle_action_addenergy_request(self, data: Dict) -> Dict:
|
||||||
uid = data['addenergy']['uid']
|
uid = data["addenergy"]["uid"]
|
||||||
self.logger.info(f"Add energy to user {uid}")
|
self.logger.info(f"Add energy to user {uid}")
|
||||||
profile = self.data.profile.get_profile_index(0, uid, self.version)
|
profile = self.data.profile.get_profile_index(0, uid, self.version)
|
||||||
data1 = profile["data"]
|
data1 = profile["data"]
|
||||||
@@ -389,12 +506,12 @@ class CxbBase():
|
|||||||
if not p:
|
if not p:
|
||||||
self.data.item.put_energy(uid, 5)
|
self.data.item.put_energy(uid, 5)
|
||||||
|
|
||||||
return({
|
return {
|
||||||
"class": data1["myClass"],
|
"class": data1["myClass"],
|
||||||
"granted": "5",
|
"granted": "5",
|
||||||
"total": "5",
|
"total": "5",
|
||||||
"threshold": "1000"
|
"threshold": "1000",
|
||||||
})
|
}
|
||||||
|
|
||||||
array = []
|
array = []
|
||||||
|
|
||||||
@@ -402,19 +519,23 @@ class CxbBase():
|
|||||||
self.data.item.put_energy(uid, newenergy)
|
self.data.item.put_energy(uid, newenergy)
|
||||||
|
|
||||||
if int(energy) <= 995:
|
if int(energy) <= 995:
|
||||||
array.append({
|
array.append(
|
||||||
|
{
|
||||||
"class": data1["myClass"],
|
"class": data1["myClass"],
|
||||||
"granted": "5",
|
"granted": "5",
|
||||||
"total": str(energy),
|
"total": str(energy),
|
||||||
"threshold": "1000"
|
"threshold": "1000",
|
||||||
})
|
}
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
array.append({
|
array.append(
|
||||||
|
{
|
||||||
"class": data1["myClass"],
|
"class": data1["myClass"],
|
||||||
"granted": "0",
|
"granted": "0",
|
||||||
"total": str(energy),
|
"total": str(energy),
|
||||||
"threshold": "1000"
|
"threshold": "1000",
|
||||||
})
|
}
|
||||||
|
)
|
||||||
return array[0]
|
return array[0]
|
||||||
|
|
||||||
def handle_action_eventreq_request(self, data: Dict) -> Dict:
|
def handle_action_eventreq_request(self, data: Dict) -> Dict:
|
||||||
|
|||||||
+29
-9
@@ -1,40 +1,60 @@
|
|||||||
from core.config import CoreConfig
|
from core.config import CoreConfig
|
||||||
|
|
||||||
class CxbServerConfig():
|
|
||||||
|
class CxbServerConfig:
|
||||||
def __init__(self, parent_config: "CxbConfig"):
|
def __init__(self, parent_config: "CxbConfig"):
|
||||||
self.__config = parent_config
|
self.__config = parent_config
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def enable(self) -> bool:
|
def enable(self) -> bool:
|
||||||
return CoreConfig.get_config_field(self.__config, 'cxb', 'server', 'enable', default=True)
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "cxb", "server", "enable", default=True
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def loglevel(self) -> int:
|
def loglevel(self) -> int:
|
||||||
return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'cxb', 'server', 'loglevel', default="info"))
|
return CoreConfig.str_to_loglevel(
|
||||||
|
CoreConfig.get_config_field(
|
||||||
|
self.__config, "cxb", "server", "loglevel", default="info"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hostname(self) -> str:
|
def hostname(self) -> str:
|
||||||
return CoreConfig.get_config_field(self.__config, 'cxb', 'server', 'hostname', default="localhost")
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "cxb", "server", "hostname", default="localhost"
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ssl_enable(self) -> bool:
|
def ssl_enable(self) -> bool:
|
||||||
return CoreConfig.get_config_field(self.__config, 'cxb', 'server', 'ssl_enable', default=False)
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "cxb", "server", "ssl_enable", default=False
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def port(self) -> int:
|
def port(self) -> int:
|
||||||
return CoreConfig.get_config_field(self.__config, 'cxb', 'server', 'port', default=8082)
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "cxb", "server", "port", default=8082
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def port_secure(self) -> int:
|
def port_secure(self) -> int:
|
||||||
return CoreConfig.get_config_field(self.__config, 'cxb', 'server', 'port_secure', default=443)
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "cxb", "server", "port_secure", default=443
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ssl_cert(self) -> str:
|
def ssl_cert(self) -> str:
|
||||||
return CoreConfig.get_config_field(self.__config, 'cxb', 'server', 'ssl_cert', default="cert/title.crt")
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "cxb", "server", "ssl_cert", default="cert/title.crt"
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ssl_key(self) -> str:
|
def ssl_key(self) -> str:
|
||||||
return CoreConfig.get_config_field(self.__config, 'cxb', 'server', 'ssl_key', default="cert/title.key")
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "cxb", "server", "ssl_key", default="cert/title.key"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CxbConfig(dict):
|
class CxbConfig(dict):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
|
|||||||
+7
-2
@@ -1,4 +1,4 @@
|
|||||||
class CxbConstants():
|
class CxbConstants:
|
||||||
GAME_CODE = "SDCA"
|
GAME_CODE = "SDCA"
|
||||||
|
|
||||||
CONFIG_NAME = "cxb.yaml"
|
CONFIG_NAME = "cxb.yaml"
|
||||||
@@ -8,7 +8,12 @@ class CxbConstants():
|
|||||||
VER_CROSSBEATS_REV_SUNRISE_S2 = 2
|
VER_CROSSBEATS_REV_SUNRISE_S2 = 2
|
||||||
VER_CROSSBEATS_REV_SUNRISE_S2_OMNI = 3
|
VER_CROSSBEATS_REV_SUNRISE_S2_OMNI = 3
|
||||||
|
|
||||||
VERSION_NAMES = ("crossbeats REV.", "crossbeats REV. SUNRISE", "crossbeats REV. SUNRISE S2", "crossbeats REV. SUNRISE S2 Omnimix")
|
VERSION_NAMES = (
|
||||||
|
"crossbeats REV.",
|
||||||
|
"crossbeats REV. SUNRISE",
|
||||||
|
"crossbeats REV. SUNRISE S2",
|
||||||
|
"crossbeats REV. SUNRISE S2 Omnimix",
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def game_ver_to_string(cls, ver: int):
|
def game_ver_to_string(cls, ver: int):
|
||||||
|
|||||||
@@ -0,0 +1,474 @@
|
|||||||
|
index,mcode,name,artist,category,easy,standard,hard,master,unlimited,
|
||||||
|
100000,tutori2,Tutorial,Tutorial,Unknown,Easy N/A,Standard N/A,Hard N/A,Master N/A,Unlimited N/A,
|
||||||
|
100000,tutori3,Tutorial,Tutorial,Unknown,Easy N/A,Standard N/A,Hard N/A,Master N/A,Unlimited N/A,
|
||||||
|
100000,tutori4,Tutorial,Tutorial,Pick-Up J-Pop (New),Easy N/A,Standard N/A,Hard N/A,Master N/A,Unlimited N/A,
|
||||||
|
100000,tutori6,Tutorial,Tutorial,Pick-Up J-Pop (New),Easy N/A,Standard N/A,Hard N/A,Master N/A,Unlimited N/A,
|
||||||
|
100000,tutori8,白鳥の湖 (Short Remix),,Original,Easy N/A,Standard 3,Hard 15,Master 35,Unlimited N/A,
|
||||||
|
100300,sateli,Satellite System ft.Diana Chiaki,GRATEC MOUR,Original,Easy 17,Standard 28,Hard 49,Master 77,Unlimited 82,
|
||||||
|
100301,nature,Human Nature,Z pinkpong,Original,Easy 5,Standard 14,Hard 24,Master 53,Unlimited 75,
|
||||||
|
100307,purple,DEEP PURPLE,NAOKI,Original,Easy 14,Standard 22,Hard 54,Master 64,Unlimited 73,
|
||||||
|
100308,hearts,Heartstrings,Nhato,Original,Easy 8,Standard 18,Hard 38,Master 68,Unlimited 77,
|
||||||
|
100310,phasea,Phase Angel,OCOT,Original,Easy 9,Standard 16,Hard 38,Master 65,Unlimited 75,
|
||||||
|
100311,planet,Planet Calling,Nyolfen,Original,Easy 10,Standard 17,Hard 36,Master 49,Unlimited 71,
|
||||||
|
100314,firefo,Firefox,Go-qualia,Original,Easy 7,Standard 13,Hard 36,Master 57,Unlimited 83,
|
||||||
|
100315,kounen,光年(konen),小野秀幸,Original,Easy 10,Standard 21,Hard 40,Master 66,Unlimited 78,
|
||||||
|
100316,essenc,Another Essence,RAM,Original,Easy 11,Standard 25,Hard 50,Master 70,Unlimited 76,
|
||||||
|
100317,summer,Summer End Anthem,Personative,Original,Easy 13,Standard 23,Hard 57,Master 79,Unlimited 89,
|
||||||
|
100319,tanosi,たのしいことだけ,Yamajet,Original,Easy 16,Standard 25,Hard 45,Master 70,Unlimited 80,
|
||||||
|
100320,picora,ピコラセテ,TORIENA,Original,Easy 8,Standard 15,Hard 38,Master 66,Unlimited 75,
|
||||||
|
100323,devils,Devil's Classic,Tatsh,Original,Easy 15,Standard 27,Hard 40,Master 80,Unlimited N/A,
|
||||||
|
100328,techno,Techno Highway,SIMON,Original,Easy 9,Standard 16,Hard 38,Master 51,Unlimited 74,
|
||||||
|
100335,glowww,GLOW,Shoichiro Hirata feat. Ellie,Original,Easy 8,Standard 17,Hard 28,Master 42,Unlimited 60,
|
||||||
|
100336,powerr,Power,Dubscribe,Original,Easy 12,Standard 19,Hard 38,Master 69,Unlimited 79,
|
||||||
|
100340,amater,Amateras,Sakuzyo,Original,Easy 13,Standard 21,Hard 48,Master 65,Unlimited 79,
|
||||||
|
100349,advers,Adverse Effect,Rin,Original,Easy 9,Standard 15,Hard 48,Master 71,Unlimited 83,
|
||||||
|
100353,venera,Venerated,Tosh,Original,Easy 8,Standard 15,Hard 43,Master 68,Unlimited 75,
|
||||||
|
100357,dazaii,堕罪,HAKKYOU-KUN feat.せつな,Original,Easy 12,Standard 21,Hard 43,Master 73,Unlimited 77,
|
||||||
|
100365,thesig,The Signs Of The Last Day,SLAKE,Original,Easy 10,Standard 21,Hard 38,Master 56,Unlimited 73,
|
||||||
|
100344,hosita,星達のメロディ,ゆいこんぬ,Original,Easy 10,Standard 16,Hard 36,Master 48,Unlimited 65,
|
||||||
|
100372,bluede,Blue Destiny Blue,NAOKI feat. Florence McNair,Original,Easy 12,Standard 22,Hard 41,Master 58,Unlimited 70,
|
||||||
|
100373,emerao,EMERALD♡KISS ~Original Side~,jun with Aimee,Original,Easy 19,Standard 30,Hard 53,Master 85,Unlimited N/A,
|
||||||
|
100129,megaro,MEGALOMAN[i]A,TITANZ,Original,Easy 0,Standard 55,Hard 80,Master 93,Unlimited 98,
|
||||||
|
100330,angeli,angelik-vice,void,Original,Easy 22,Standard 33,Hard 56,Master 82,Unlimited 90,
|
||||||
|
100342,moonli,月鳴 -moonlit urge-,AZURE FACTORY,Original,Easy 8,Standard 14,Hard 43,Master 61,Unlimited 73,
|
||||||
|
100369,yumemi,ユメミル船,yozuca*,Original,Easy 6,Standard 12,Hard 35,Master 59,Unlimited 69,
|
||||||
|
100348,pinkym,Pinky Magic,Junk,Original,Easy 16,Standard 24,Hard 44,Master 74,Unlimited 81,
|
||||||
|
100370,dynami2,DYNAMITE SENSATION REV.,NAOKI feat. Hyphen,Original,Easy 8,Standard 18,Hard 51,Master 78,Unlimited 80,
|
||||||
|
100306,reseed3,Reseed (Another Edit),quick master,Original,Easy 10,Standard 20,Hard 55,Master 76,Unlimited 80,
|
||||||
|
100002,toucho,Touch Of Gold,Togo Project feat. Frances Maya,Original,Easy 5,Standard 9,Hard 28,Master 44,Unlimited 65,
|
||||||
|
100003,ameoto,雨の音が虹を呼ぶ,Barbarian On The Groove feat.霜月はるか,Original,Easy 6,Standard 12,Hard 26,Master 47,Unlimited 63,
|
||||||
|
100004,kimito,キミとMUSIC,CooRie,Original,Easy 7,Standard 10,Hard 26,Master 49,Unlimited 66,
|
||||||
|
100021,giantk,Giant Killing,R-Lab,Original,Easy 11,Standard 25,Hard 53,Master 71,Unlimited 78,
|
||||||
|
100015,breakd,Break down,GARNiDELiA,Original,Easy 11,Standard 23,Hard 34,Master 57,Unlimited 74,
|
||||||
|
100028,dazzlj,DAZZLING♡SEASON (Japanese Side),jun,Original,Easy 16,Standard 35,Hard 60,Master 80,Unlimited 90,
|
||||||
|
100093,ididid,I.D.,Tatsh feat. 彩音,Original,Easy 16,Standard 29,Hard 46,Master 72,Unlimited 81,
|
||||||
|
100042,sundro,Sundrop,Yamajet,Original,Easy 14,Standard 24,Hard 47,Master 75,Unlimited 83,
|
||||||
|
100063,auflcb,some day (instrumental),NAOKI,Original,Easy 8,Standard 13,Hard 43,Master 81,Unlimited N/A,
|
||||||
|
100045,dennou,電脳少女は歌姫の夢を見るか?,デスおはぎ feat.蛮,Original,Easy 15,Standard 29,Hard 60,Master 76,Unlimited 87,
|
||||||
|
100068,hokoro,ホコロビシロガールズ,むかしばなし,Original,Easy 14,Standard 29,Hard 57,Master 71,Unlimited 81,
|
||||||
|
100005,landin,Landing on the moon,SIMON,Original,Easy 13,Standard 26,Hard 33,Master 49,Unlimited 67,
|
||||||
|
100362,tomorr,Tomorrow,桜井零士,Original,Easy 8,Standard 15,Hard 24,Master 44,Unlimited 62,
|
||||||
|
100363,daybyd,day by day,海辺,Original,Easy 6,Standard 13,Hard 26,Master 38,Unlimited 59,
|
||||||
|
100309,syoujo,生々世々,SADA,Original,Easy 8,Standard 19,Hard 35,Master 53,Unlimited 78,
|
||||||
|
100352,destru,Destrudo,D-Fener,Original,Easy 10,Standard 19,Hard 41,Master 62,Unlimited 72,
|
||||||
|
100041,gingat,Re:Milky way,イトヲカシ,Original,Easy 5,Standard 13,Hard 29,Master 46,Unlimited 61,
|
||||||
|
100066,daisak,大殺界がらくたシンパシー,まふまふ,Original,Easy 12,Standard 28,Hard 36,Master 60,Unlimited 75,
|
||||||
|
100376,paradi,Paradise Regained,LC:AZE feat.chakk,Original,Easy 10,Standard 16,Hard 28,Master 53,Unlimited 64,
|
||||||
|
100377,pigooo,PIG-O,NNNNNNNNNN,Original,Easy 13,Standard 19,Hard 34,Master 59,Unlimited 84,
|
||||||
|
100386,season,The Four Seasons -SPRING- (Remix Ver.),,Variety,Easy 8,Standard 15,Hard 28,Master 44,Unlimited 65,
|
||||||
|
100387,canonn,カノン (Remix Ver.),,Variety,Easy 7,Standard 17,Hard 28,Master 50,Unlimited 83,
|
||||||
|
100388,rhapso,Rhapsody in Blue (Remix Ver.),,Variety,Easy 6,Standard 18,Hard 34,Master 62,Unlimited 74,
|
||||||
|
100389,turkis,トルコ行進曲 (Remix Ver.),,Variety,Easy 11,Standard 19,Hard 39,Master 64,Unlimited 84,
|
||||||
|
100390,biohaz,code_,umbrella Cores,Variety,Easy 6,Standard 15,Hard 30,Master 51,Unlimited 64,
|
||||||
|
100391,monhan,英雄の証 ~ 4Version,カプコンサウンドチーム,Variety,Easy 5,Standard 10,Hard 26,Master 36,Unlimited 54,
|
||||||
|
100392,gyakut2,追求 ~最終プロモーションバージョン (crossbeats REV.アレンジ),岩垂 徳行,Variety,Easy 5,Standard 13,Hard 35,Master 43,Unlimited 56,
|
||||||
|
100393,street,Theme of Ryu -SFIV Arrange-,Capcom Sound Team / Hideyuki Fukasawa,Variety,Easy 7,Standard 13,Hard 34,Master 47,Unlimited 66,
|
||||||
|
100394,rockma2,Dr. WILY STAGE 1 -OMEGAMAN MIX-,ROCK-MEN,Variety,Easy 14,Standard 21,Hard 34,Master 49,Unlimited 76,
|
||||||
|
100374,auflcb3,SOMEDAY -00.prologue-,TЁЯRA,Original,Easy 6,Standard 16,Hard 36,Master 66,Unlimited 86,
|
||||||
|
100325,irohaa,Iroha,Ryunosuke Kudo,Original,Easy 12,Standard 19,Hard 41,Master 55,Unlimited 76,
|
||||||
|
100326,ibelie,I Believe Someday,SPARKER,Original,Easy 14,Standard 27,Hard 47,Master 78,Unlimited 82,
|
||||||
|
100409,monhan2,灼熱の刃 ~ ディノバルド,カプコンサウンドチーム,Variety,Easy 6,Standard 12,Hard 24,Master 43,Unlimited 68,
|
||||||
|
100410,monhan3,古代の息吹き,カプコンサウンドチーム,Variety,Easy 8,Standard 18,Hard 28,Master 45,Unlimited 73,
|
||||||
|
100418,yejiii,YEJI,ginkiha,Original,Easy 10,Standard 22,Hard 36,Master 63,Unlimited 79,
|
||||||
|
100419,histor,HISTORIA,Cranky,Original,Easy 11,Standard 20,Hard 36,Master 56,Unlimited 82,
|
||||||
|
100338,chaset,Chase the WAVE,Tatsh feat. AKINO with bless4,Original,Easy 8,Standard 15,Hard 31,Master 58,Unlimited 76,
|
||||||
|
100412,metall,Metallical parade,Vice Principal,Original,Easy 8,Standard 16,Hard 28,Master 57,Unlimited 77,
|
||||||
|
100327,letmeg,Let Me Give You My Heart,brinq,Original,Easy 12,Standard 18,Hard 32,Master 50,Unlimited 72,
|
||||||
|
100010,hontno,ホントのワタシ,mao,Original,Easy 9,Standard 12,Hard 26,Master 53,Unlimited 66,
|
||||||
|
100024,azitat,Azitate,void,Original,Easy 14,Standard 24,Hard 55,Master 70,Unlimited 83,
|
||||||
|
100360,hellom,Hello Mr.crosbie,民安★ROCK,Original,Easy 7,Standard 18,Hard 37,Master 58,Unlimited 72,
|
||||||
|
100337,laught,Perfect laughter,ぽんず loved by yksb,Original,Easy 7,Standard 20,Hard 35,Master 51,Unlimited 71,
|
||||||
|
100426,bluede2,Blue Destiny Blue ETERNAL,NAOKI feat. Florence McNair,Original,Easy 9,Standard 16,Hard 36,Master 56,Unlimited 81,
|
||||||
|
100423,street2,Ultra Street Fighter IV,Hideyuki Fukasawa,Variety,Easy 14,Standard 21,Hard 37,Master 55,Unlimited 73,
|
||||||
|
100424,street3,Theme of Chun-Li -SFIV Arrange-,Capcom Sound Team / Hideyuki Fukasawa,Variety,Easy 13,Standard 24,Hard 38,Master 57,Unlimited 74,
|
||||||
|
100425,street4,Street Fighter V,Masahiro Aoki,Variety,Easy 11,Standard 17,Hard 32,Master 51,Unlimited 78,
|
||||||
|
100421,silbur,Silbury Sign,カヒーナムジカ,Original,Easy 9,Standard 20,Hard 35,Master 54,Unlimited 75,
|
||||||
|
100422,spicaa,Spica,Endorfin.,Original,Easy 10,Standard 23,Hard 36,Master 56,Unlimited 78,
|
||||||
|
100438,tricko,Trick Or Treat,SLAKE,Original,Easy 11,Standard 23,Hard 34,Master 56,Unlimited 75,
|
||||||
|
100435,thisis,THIS IS HDM,Relect,Original,Easy 12,Standard 23,Hard 37,Master 60,Unlimited 74,
|
||||||
|
100436,rising,Rising Day ft. Satan,GRATEC MOUR,Original,Easy 14,Standard 23,Hard 38,Master 66,Unlimited 85,
|
||||||
|
100411,orbita,Orbital velocity,Vice Principal,Original,Easy 12,Standard 20,Hard 38,Master 62,Unlimited 74,
|
||||||
|
100433,dddddd,D,六弦アリス,Original,Easy 9,Standard 13,Hard 33,Master 53,Unlimited 69,
|
||||||
|
100427,pyroma,Pyromania,KO3,Original,Easy 8,Standard 22,Hard 42,Master 70,Unlimited 84,
|
||||||
|
100312,touchn,Touch n Go,Paisley Parks,Original,Easy 15,Standard 27,Hard 46,Master 68,Unlimited 86,
|
||||||
|
100359,onlyll,only L,emon,Original,Easy 13,Standard 21,Hard 32,Master 56,Unlimited 69,
|
||||||
|
100313,upside,Upside Down,Nave ft.Mayu Wakisaka,Original,Easy 8,Standard 18,Hard 27,Master 48,Unlimited 67,
|
||||||
|
100322,istanb,İstanbul,REVen-G,Original,Easy 23,Standard 41,Hard 49,Master 90,Unlimited 98,
|
||||||
|
100371,memori,Memoria ~終焉を司る荊姫の静粛なる宴~,Astilbe × arendsii,Original,Easy 13,Standard 26,Hard 38,Master 65,Unlimited 84,
|
||||||
|
100350,straye,Strayer,Taishi,Original,Easy 14,Standard 25,Hard 36,Master 61,Unlimited 73,
|
||||||
|
100358,rearhy,Rearhythm,CooRie,Original,Easy 7,Standard 17,Hard 32,Master 52,Unlimited 69,
|
||||||
|
100432,hereco,Here comes the sun ~For you~,Z pinkpong,Original,Easy 11,Standard 16,Hard 34,Master 51,Unlimited 68,
|
||||||
|
100441,thesun,THE SUN,Tatsh,Original,Easy 13,Standard 25,Hard 40,Master 72,Unlimited 87,
|
||||||
|
100343,sayona,さよなら最終列車,むかしばなし,Original,Easy 10,Standard 20,Hard 34,Master 53,Unlimited 71,
|
||||||
|
100380,flameu,Flame Up,Inu Machine,Original,Easy 10,Standard 18,Hard 36,Master 57,Unlimited 65,
|
||||||
|
100434,raidon,RAiD on Mars,sky_delta,Original,Easy 13,Standard 30,Hard 38,Master 58,Unlimited 87,
|
||||||
|
100437,riseup,Rise Up,Dubscribe,Original,Easy 7,Standard 18,Hard 41,Master 67,Unlimited 77,
|
||||||
|
100431,sunglo,Sunglow,Yamajet feat. ひうらまさこ,Original,Easy 10,Standard 18,Hard 39,Master 59,Unlimited 72,
|
||||||
|
100439,kinbos,金星(kinboshi),Hideyuki Ono,Original,Easy 12,Standard 22,Hard 38,Master 64,Unlimited 77,
|
||||||
|
100430,densho,電脳少女と機械仕掛けの神,Chimera music.,Original,Easy 17,Standard 28,Hard 42,Master 74,Unlimited 90,
|
||||||
|
100471,aiohoo,愛をほおばりたいッ!~Like a Monkey!~,新堂敦士,J-Pop,Easy 10,Standard 18,Hard 29,Master 42,Unlimited 66,
|
||||||
|
100472,entert,エンターテイナー (Remix ver.),,Variety,Easy 9,Standard 15,Hard 33,Master 54,Unlimited 87,
|
||||||
|
100457,takeit,Take It Back,Daniel Seven,Original,Easy 12,Standard 20,Hard 38,Master 65,Unlimited 83,
|
||||||
|
100449,harmon,Harmony,ピクセルビー,Original,Easy 12,Standard 20,Hard 36,Master 49,Unlimited 65,
|
||||||
|
100428,avemar,アヴェ・マリア (Remix ver.),,Variety,Easy 8,Standard 16,Hard 31,Master 53,Unlimited 75,
|
||||||
|
100429,mateki,復讐の炎は地獄のように我が心に燃え (Remix ver.),,Variety,Easy 10,Standard 18,Hard 35,Master 54,Unlimited 79,
|
||||||
|
100445,lovech,LOVE CHASE,大島はるな,Original,Easy 8,Standard 16,Hard 30,Master 46,Unlimited 68,
|
||||||
|
100473,akaihe,赤いヘッドホン,新堂敦士,J-Pop,Easy 8,Standard 21,Hard 32,Master 50,Unlimited 69,
|
||||||
|
100474,juicys,Juicy! ~幸せスパイラル~,新堂敦士,J-Pop,Easy 10,Standard 18,Hard 26,Master 45,Unlimited 83,
|
||||||
|
100468,codena,CODENAMEはEQ,TORIENA,Original,Easy 9,Standard 18,Hard 35,Master 48,Unlimited 68,
|
||||||
|
100475,groove,LINK LINK FEVER!!!(グルーヴコースター 3 リンクフィーバーより),リンカ (CV:豊田萌絵),Variety,Easy 10,Standard 22,Hard 40,Master 52,Unlimited 73,
|
||||||
|
100450,kansho,観賞用マーメイド,ヤマイ,Original,Easy 7,Standard 13,Hard 27,Master 55,Unlimited 74,
|
||||||
|
100486,overcl2,Over Clock ~前兆~,NAOKI feat. un∞limited,Original,Easy 12,Standard 23,Hard 42,Master 58,Unlimited 74,
|
||||||
|
100483,taikoo,SAKURA EXHAUST,RIO HAMAMOTO(BNSI)「太鼓の達人」より,Variety,Easy 6,Standard 13,Hard 39,Master 50,Unlimited 75,
|
||||||
|
100480,groove2,QLWA(グルーヴコースター 3 リンクフィーバーより),t+pazolite,Variety,Easy 9,Standard 15,Hard 40,Master 58,Unlimited 85,
|
||||||
|
100487,overcl,Over Clock ~開放~,NAOKI feat. un∞limited,Original,Easy 8,Standard 12,Hard 35,Master 57,Unlimited 86,
|
||||||
|
100466,notoss,Notos,ginkiha,Original,Easy 8,Standard 12,Hard 42,Master 65,Unlimited 91,
|
||||||
|
100447,machup,マチュ☆ピチュ,コツキミヤ,Original,Easy 9,Standard 17,Hard 32,Master 41,Unlimited 71,
|
||||||
|
100488,groove3,カリソメ(グルーヴコースター 3 リンクフィーバーより),コンプ(豚乙女) × ichigo(岸田教団 & THE明星ロケッツ),Touhou + Variety,Easy 10,Standard 18,Hard 34,Master 64,Unlimited 85,
|
||||||
|
100489,groove4,そして誰もいなくなった(グルーヴコースター 3 リンクフィーバーより),コバヤシユウヤ(IOSYS) × あにー(TaNaBaTa),Touhou + Variety,Easy 12,Standard 22,Hard 35,Master 50,Unlimited 75,
|
||||||
|
100482,everyt,EVERYTHING,Tatsh feat.小田ユウ,Original,Easy 13,Standard 22,Hard 30,Master 74,Unlimited N/A,
|
||||||
|
100465,lespri,L'esprit,Cosine,Original,Easy 13,Standard 25,Hard 57,Master 80,Unlimited N/A,
|
||||||
|
100491,groove5,グルーヴ・ザ・ハート(グルーヴコースター 3 リンクフィーバーより),ビートまりお+あまね,Variety,Easy 14,Standard 24,Hard 37,Master 67,Unlimited N/A,
|
||||||
|
100490,honeyo,HONEY♡SUNRiSE ~Original Side~,jun with Aimee,Original,Easy 24,Standard 32,Hard 63,Master 88,Unlimited 93,
|
||||||
|
100494,groove6,Got hive of Ra(グルーヴコースター 3 リンクフィーバーより),E.G.G.,Variety,Easy 22,Standard 30,Hard 64,Master 79,Unlimited N/A,
|
||||||
|
100495,sunglo2,Sunglow (Happy Hardcore Style),Yamajet feat. ひうらまさこ,Original,Easy 11,Standard 21,Hard 36,Master 67,Unlimited 81,
|
||||||
|
100498,fourte,14th Clock,INNOCENT NOIZE,Original,Easy 14,Standard 24,Hard 50,Master 74,Unlimited 80,
|
||||||
|
100496,monhan4,英雄の証/MHF-G 2015 Version,若林タカツグ,Variety,Easy 5,Standard 12,Hard 40,Master 51,Unlimited 62,
|
||||||
|
100497,monhan5,異ヲ辿リシモノ -対峙-,若林タカツグ,Variety,Easy 10,Standard 12,Hard 35,Master 42,Unlimited 65,
|
||||||
|
100504,darkpa,Dark Parashu,INNOCENT NOIZE,Original,Easy 16,Standard 26,Hard 39,Master 70,Unlimited 84,
|
||||||
|
100505,hervor,Hervor,INNOCENT NOIZE,Original,Easy 18,Standard 28,Hard 39,Master 73,Unlimited 81,
|
||||||
|
100499,cirnon,チルノのパーフェクトさんすう教室,ARM+夕野ヨシミ (IOSYS) feat. miko,Touhou,Easy 17,Standard 24,Hard 40,Master 60,Unlimited 79,
|
||||||
|
100500,marisa,魔理沙は大変なものを盗んでいきました,ARM+夕野ヨシミ (IOSYS) feat. 藤咲かりん,Touhou,Easy 18,Standard 25,Hard 41,Master 62,Unlimited 85,
|
||||||
|
100501,yakini,究極焼肉レストラン!お燐の地獄亭!,ARM+夕野ヨシミ (IOSYS) feat. 藤枝あかね,Touhou,Easy 13,Standard 25,Hard 34,Master 58,Unlimited 82,
|
||||||
|
100502,justic,ジャスティス・オブ・ザ・界隈 ~ALL IS FAIR IN LOVE AND ALIMARI~,void (IOSYS) feat.山本椛,Touhou,Easy 14,Standard 19,Hard 36,Master 57,Unlimited 83,
|
||||||
|
100503,sintyo,進捗どうですか?,sumijun feat.ななひら,Touhou,Easy 16,Standard 25,Hard 46,Master 70,Unlimited 83,
|
||||||
|
100347,ascand,Ascendanz,void,Original,Easy 18,Standard 32,Hard 54,Master 80,Unlimited 90,
|
||||||
|
100506,blackl,Black Lotus,Maozon,Original,Easy 12,Standard 19,Hard 41,Master 73,Unlimited 84,
|
||||||
|
100043,childr,チルドレン・オートマトン~ある歌声の亡霊~,あさまっく,Original,Easy 14,Standard 24,Hard 39,Master 56,Unlimited 62,
|
||||||
|
100044,tsukai,ツカイステ・デッドワールド,コゲ犬×ゆちゃ,Original,Easy 13,Standard 19,Hard 44,Master 72,Unlimited 76,
|
||||||
|
100067,rideon,RIDE ON NOW!,さつき が てんこもり feat.un:c,Original,Easy 16,Standard 29,Hard 41,Master 60,Unlimited 80,
|
||||||
|
100507,minest,Minestrone,orangentle,Original,Easy 13,Standard 21,Hard 39,Master 62,Unlimited 76,
|
||||||
|
100508,ordine,Ordine,orangentle,Original,Easy 19,Standard 25,Hard 43,Master 73,Unlimited 82,
|
||||||
|
100509,dreamw,DReamWorKer,LC:AZE,Original,Easy 16,Standard 26,Hard 37,Master 62,Unlimited 75,
|
||||||
|
100510,minerv,Minerva,xi,Original,Easy 25,Standard 32,Hard 61,Master 90,Unlimited N/A,
|
||||||
|
100001,wannab,Wanna Be Your Special,Shoichiro Hirata feat. SUIMI,Original,Easy 5,Standard 9,Hard 23,Master 40,Unlimited 65,
|
||||||
|
100511,sekain,世界の果て,Yamajet,Original,Easy 16,Standard 26,Hard 39,Master 69,Unlimited 78,
|
||||||
|
100512,farawa,Faraway,ミフメイ,Original,Easy 18,Standard 23,Hard 36,Master 60,Unlimited 76,
|
||||||
|
100100,crissc,Crisscrosser,void,Original,Easy 17,Standard 37,Hard 63,Master 86,Unlimited 91,
|
||||||
|
100324,speedy,Awake Speedy,DJ MURASAME,Original,Easy 11,Standard 22,Hard 55,Master 77,Unlimited N/A,
|
||||||
|
100513,xxxrev,XXX-revolt,void feat. KOTOKO,Original,Easy 15,Standard 21,Hard 34,Master 56,Unlimited 73,
|
||||||
|
100016,higame,Hi,Go-qualia,Original,Easy 13,Standard 20,Hard 30,Master 58,Unlimited 71,
|
||||||
|
100022,theepi,The Epic,Cranky,Original,Easy 14,Standard 19,Hard 40,Master 61,Unlimited 75,
|
||||||
|
100023,anomie,Anomie,D-Fener,Original,Easy 15,Standard 22,Hard 38,Master 61,Unlimited 77,
|
||||||
|
100524,crocus,Crocus,村瀬悠太,Original,Easy 15,Standard 26,Hard 37,Master 60,Unlimited 72,
|
||||||
|
100546,lavien,La vie en Fleurs,VILA,Original,Easy 18,Standard 27,Hard 41,Master 71,Unlimited 80,
|
||||||
|
100361,megaro2,MEGALOMAN[i]A -2nd IMPACT-,NEO-G,Original,Easy N/A,Standard N/A,Hard N/A,Master 99,Unlimited 100,
|
||||||
|
100541,chipnn,Chip Notch Educ@tion,yaseta feat. chip_Notch,Original,Easy 16,Standard 27,Hard 34,Master 61,Unlimited 79,
|
||||||
|
100007,yiyoyi,Wanyo Wanyo,MC Natsack,Original,Easy 7,Standard 14,Hard 33,Master 56,Unlimited 70,
|
||||||
|
100014,binary,Binary Overdrive,フラット3rd,Original,Easy 14,Standard 17,Hard 35,Master 64,Unlimited 89,
|
||||||
|
100054,makaim,魔界村 (平地BGM),Remixed by ARM (IOSYS),Original + Variety,Easy 23,Standard 30,Hard 50,Master 77,Unlimited N/A,
|
||||||
|
100055,gyakut,逆転裁判 (綾里真宵 ~逆転姉妹のテーマ),Remixed by OSTER project,Original + Variety,Easy 6,Standard 15,Hard 21,Master 46,Unlimited 64,
|
||||||
|
100056,basara,戦国BASARA (SENGOKU BASARA),Remixed by SOUND HOLIC,Original + Variety,Easy 14,Standard 19,Hard 37,Master 64,Unlimited 73,
|
||||||
|
100514,daybre,DAYBREAK FRONTLINE,Orangestar,Vocaloid,Easy 9,Standard 16,Hard 32,Master 53,Unlimited 72,
|
||||||
|
100515,umiyur,ウミユリ海底譚,n-buna,Vocaloid,Easy 8,Standard 14,Hard 28,Master 46,Unlimited 64,
|
||||||
|
100516,chalur,シャルル,バルーン,Vocaloid,Easy 14,Standard 18,Hard 40,Master 60,Unlimited 72,
|
||||||
|
100517,melanc,メランコリック,Junky,Vocaloid,Easy 10,Standard 15,Hard 30,Master 50,Unlimited 63,
|
||||||
|
100518,konofu,このふざけた素晴らしき世界は、僕の為にある,n.k,Vocaloid,Easy 11,Standard 23,Hard 35,Master 62,Unlimited 81,
|
||||||
|
100526,bladem,The Blade Master,mikashu,Original,Easy 17,Standard 28,Hard 38,Master 63,Unlimited 75,
|
||||||
|
100536,southw,South wind,moimoi,Original,Easy 12,Standard 18,Hard 27,Master 57,Unlimited 68,
|
||||||
|
100537,ryuuse,流星デモクラシー,kamejack,Original,Easy 13,Standard 21,Hard 32,Master 59,Unlimited N/A,
|
||||||
|
100519,redhea,ROCK'N'ROLL☆FLYING REDHEAD,暁Records,Touhou,Easy 10,Standard 27,Hard 39,Master 59,Unlimited 72,
|
||||||
|
100520,warnin,WARNING×WARNING×WARNING,暁Records,Touhou,Easy 12,Standard 25,Hard 36,Master 61,Unlimited 74,
|
||||||
|
100521,topsec,TOP SECRET -My Red World-,暁Records,Touhou,Easy 13,Standard 24,Hard 34,Master 51,Unlimited 64,
|
||||||
|
100522,dddoll,DOWN DOWN DOLL,暁Records,Touhou,Easy 14,Standard 26,Hard 38,Master 55,Unlimited 63,
|
||||||
|
100548,tracee,トレイス・エゴイズム,暁Records,Touhou,Easy 9,Standard 19,Hard 31,Master 49,Unlimited 65,
|
||||||
|
100111,drivin,Driving story,Duca,Original,Easy 8,Standard 23,Hard 40,Master 66,Unlimited 76,
|
||||||
|
100118,genzit,現実幻覚スピードスター,yozuca*,Original,Easy 12,Standard 18,Hard 46,Master 73,Unlimited 82,
|
||||||
|
100039,aerial,エアリアル,カヒーナムジカ,Original,Easy 5,Standard 11,Hard 28,Master 56,Unlimited 71,
|
||||||
|
100532,einher,Einherjar,閣下,Original,Easy 16,Standard 29,Hard 40,Master 74,Unlimited 80,
|
||||||
|
100540,ariell,Ariel,nanobii,Original,Easy 15,Standard 19,Hard 32,Master 64,Unlimited 73,
|
||||||
|
100542,firstl,First Love,UFO,Original,Easy 17,Standard 25,Hard 36,Master 65,Unlimited 77,
|
||||||
|
100550,heartl,Heartland,Bernis,Original,Easy 11,Standard 23,Hard 30,Master 64,Unlimited N/A,
|
||||||
|
100551,erasee,ERASE,MozSound,Original,Easy 12,Standard 22,Hard 35,Master 58,Unlimited 68,
|
||||||
|
100530,regene,Regeneration ray,Tsukasa,Original,Easy 13,Standard 20,Hard 30,Master 56,Unlimited 70,
|
||||||
|
100549,allelu,アレルヤ,HAKKYOU-KUN feat.玉置成実,Original,Easy 16,Standard 28,Hard 35,Master 64,Unlimited 75,
|
||||||
|
100543,lighto,Light of my Life,S3RL,Original,Easy 12,Standard 25,Hard 33,Master 60,Unlimited 74,
|
||||||
|
100552,termin,Terminus a quo,ginkiha,Original,Easy 13,Standard 24,Hard 34,Master 63,Unlimited 79,
|
||||||
|
100556,ryuuse2,流星でもくらちー☆,kamejack,Original,Easy 13,Standard 20,Hard 36,Master 62,Unlimited 75,
|
||||||
|
100547,prizmm,PRIZM,ミフメイ,Original,Easy 12,Standard 21,Hard 30,Master 54,Unlimited N/A,
|
||||||
|
100098,samalv,サマ★ラブ,コツキミヤ,Original,Easy 13,Standard 19,Hard 39,Master 58,Unlimited 77,
|
||||||
|
100544,palpit,Palpitation,Zekk,Original,Easy 18,Standard 29,Hard 55,Master 84,Unlimited 92,
|
||||||
|
100558,gainen,Break the Wall!! ~ロンリガイネン,暁Records,Original,Easy 15,Standard 26,Hard 37,Master 63,Unlimited N/A,
|
||||||
|
100525,moonsh,Moon Shard,satella,Original,Easy 10,Standard 23,Hard 36,Master 62,Unlimited N/A,
|
||||||
|
100559,moonki,MoonLightKiss,effe,Original,Easy 13,Standard 25,Hard 39,Master 64,Unlimited N/A,
|
||||||
|
100560,moonri,Moonrise,Relect,Original,Easy 14,Standard 21,Hard 38,Master 58,Unlimited 85,
|
||||||
|
100561,goaway,Go Away,Cranky,Original,Easy 10,Standard 23,Hard 45,Master 59,Unlimited 70,
|
||||||
|
100567,itback,Bring it back now,siromaru,Original,Easy 12,Standard 23,Hard 38,Master 71,Unlimited N/A,
|
||||||
|
100569,redhhh,Red Heart,Yooh vs. siromaru,Original,Easy 13,Standard 24,Hard 39,Master 77,Unlimited N/A,
|
||||||
|
100568,actual,Actual Reverse,siromaru,Original,Easy 14,Standard 25,Hard 38,Master 80,Unlimited N/A,
|
||||||
|
100367,zonzon,Bi-Zon Zon Zombi,MC Natsack,Original,Easy 5,Standard 16,Hard 33,Master 63,Unlimited 67,
|
||||||
|
100565,memorm,Memorim,Avans,Original,Easy 15,Standard 26,Hard 37,Master 73,Unlimited N/A,
|
||||||
|
100554,kokoro,ココロメソッド,Endorfin.,Original,Easy 12,Standard 20,Hard 43,Master 65,Unlimited 69,
|
||||||
|
100563,poweri,Power is Power,KO3,Original,Easy 13,Standard 26,Hard 49,Master 75,Unlimited 91,
|
||||||
|
100555,nisenn,2020,Ω,Original,Easy N/A,Standard N/A,Hard N/A,Master 76,Unlimited N/A,
|
||||||
|
100096,yukiya,Vespero,Monotone Rhythm feat.綾川雪弥,Original,Easy 11,Standard 19,Hard 40,Master 61,Unlimited N/A,
|
||||||
|
100124,zankyo,残響のアカーシャ,Astilbe × arendsii,Original,Easy 10,Standard 18,Hard 38,Master 57,Unlimited 74,
|
||||||
|
100119,overlp,オーバーラップ,millie loved by yksb,Original,Easy 9,Standard 17,Hard 30,Master 51,Unlimited N/A,
|
||||||
|
100529,fracta,Fractalize,Sakuzyo,Original,Easy 19,Standard 31,Hard 52,Master 83,Unlimited N/A,
|
||||||
|
100455,cantst,Can't Stop,KaSa,Original,Easy 11,Standard 23,Hard 42,Master 65,Unlimited N/A,
|
||||||
|
100527,primaa,Prima,Kiryu(桐生),Original,Easy 12,Standard 18,Hard 35,Master 54,Unlimited 75,
|
||||||
|
100448,cyberg,CYBER GANG,ヒゲドライVAN,Original,Easy 12,Standard 23,Hard 35,Master 60,Unlimited N/A,
|
||||||
|
100018,freakw,Freak With Me,SLAKE,Original,Easy 13,Standard 22,Hard 42,Master 65,Unlimited 66,
|
||||||
|
100006,aquali,Aqualight,MAYA AKAI,Original,Easy 11,Standard 16,Hard 34,Master 58,Unlimited N/A,
|
||||||
|
100572,takesc,Music Takes Control,Fierce Chain,Original,Easy 10,Standard 27,Hard 37,Master 69,Unlimited N/A,
|
||||||
|
100531,cthugh,Cthugha,MozSound,Original,Easy 14,Standard 25,Hard 48,Master 73,Unlimited N/A,
|
||||||
|
100571,thetaa,θ (theta) ,effe,Original,Easy 11,Standard 21,Hard 34,Master 62,Unlimited N/A,
|
||||||
|
100493,nekofu,ネコふんじゃった☆ (クローニャSTYLE),,Variety,Easy 10,Standard 22,Hard 34,Master 57,Unlimited 80,
|
||||||
|
100057,howtru,How True Is Your Love,brinq,Original,Easy 8,Standard 12,Hard 25,Master 53,Unlimited 74,
|
||||||
|
100047,romanc,ロマンシングゲーム,まふ×ティン,Original,Easy 10,Standard 28,Hard 55,Master 78,Unlimited N/A,
|
||||||
|
100573,kotobu,KOTOBUKI,REVen-G,Original,Easy 25,Standard 32,Hard 71,Master 90,Unlimited N/A,
|
||||||
|
100417,xmasss,ジングルベル (NM REMIX),,Variety,Easy 8,Standard 18,Hard 38,Master 56,Unlimited 77,
|
||||||
|
100600,galaxy,GALAXY,キュウソネコカミ,J-Pop,Easy 10,Standard 16,Hard 32,Master 43,Unlimited 67,
|
||||||
|
100601,rebell,Rebellion,NAOKI underground,Original,Easy N/A,Standard 49,Hard 63,Master 91,Unlimited N/A,
|
||||||
|
100602,anothe,Another Chance,Luci,Original,Easy N/A,Standard 27,Hard 37,Master 73,Unlimited 76,
|
||||||
|
100603,addict,Addicted,luz×アリエP,Original,Easy N/A,Standard 20,Hard 34,Master 52,Unlimited 62,
|
||||||
|
100604,dirtyy,Dirty Mouth,Asletics,Original,Easy N/A,Standard 15,Hard 28,Master 59,Unlimited 74,
|
||||||
|
100605,levelf,LEVEL5-Judgelight-,fripSide,J-Pop,Easy 5,Standard 11,Hard 28,Master 45,Unlimited 63,
|
||||||
|
100606,omnive,Omniverse,Atomic,Original,Easy N/A,Standard 34,Hard 52,Master 83,Unlimited 86,
|
||||||
|
100607,kakuse,覚醒 ∞ awake!,PwD,Original,Easy N/A,Standard 17,Hard 55,Master 75,Unlimited N/A,
|
||||||
|
100608,unbeli,アンビリーバーズ,米津玄師,J-Pop,Easy 7,Standard 13,Hard 26,Master 38,Unlimited 62,
|
||||||
|
100609,sonzai,ソンザイキョウドウタイ,ジギル,Original,Easy N/A,Standard 26,Hard 40,Master 59,Unlimited 66,
|
||||||
|
100610,okonik,OKONIKUKOD,SHU OKUYAMA,Original,Easy N/A,Standard 26,Hard 45,Master 67,Unlimited N/A,
|
||||||
|
100611,crssho,CrossShooter,Tatsh,Original,Easy 10,Standard 35,Hard 60,Master 85,Unlimited N/A,
|
||||||
|
100612,reanim,Reanimation,DC feat.S!N,Original,Easy N/A,Standard 28,Hard 44,Master 70,Unlimited 80,
|
||||||
|
100613,kamino,kaminoko,HAKKYOU-KUN,Original,Easy 15,Standard 40,Hard 62,Master 78,Unlimited N/A,
|
||||||
|
100614,fiveee,Five,ANOTHER STORY,J-Pop,Easy 10,Standard 18,Hard 37,Master 61,Unlimited 71,
|
||||||
|
100615,granda,Grand Arc,Tosh,Original,Easy N/A,Standard 21,Hard 38,Master 79,Unlimited N/A,
|
||||||
|
100616,fronti2,NEXT FRONTIER -TRUE RISE-,NAOKI,Original,Easy 9,Standard 46,Hard 69,Master 89,Unlimited N/A,
|
||||||
|
100617,saigon,最後の1ページ,桜井零士,Original,Easy N/A,Standard 19,Hard 31,Master 57,Unlimited N/A,
|
||||||
|
100618,replay,REPLAY,VAMPS,J-Pop,Easy 8,Standard 18,Hard 44,Master 63,Unlimited 70,
|
||||||
|
100619,mousou,妄想全開,志麻×ふぉP,Original,Easy N/A,Standard 16,Hard 26,Master 54,Unlimited N/A,
|
||||||
|
100620,aheadd,AHEAD,VAMPS,J-Pop,Easy 7,Standard 13,Hard 25,Master 35,Unlimited 58,
|
||||||
|
100621,musicr1,All You Need Is Beat(s) -musicるTV・ミリオン連発音楽作家塾第7弾-,CLONE,Original,Easy 12,Standard 22,Hard 33,Master 58,Unlimited 74,
|
||||||
|
100622,getthe,Get the glory,中ノ森文子,J-Pop,Easy 6,Standard 17,Hard 37,Master 49,Unlimited 66,
|
||||||
|
100623,design,Designed World,Alinut,Original,Easy N/A,Standard 15,Hard 39,Master 68,Unlimited 69,
|
||||||
|
100624,garnet,GARNET HOWL,フラット3rd,Original,Easy N/A,Standard 26,Hard 46,Master 70,Unlimited 94,
|
||||||
|
100625,hopesb,Hopes Bright,WHITE ASH,J-Pop,Easy 7,Standard 10,Hard 25,Master 44,Unlimited 61,
|
||||||
|
100626,shooti,Shooting Star feat.HISASHI (GLAY),96猫,J-Pop,Easy 7,Standard 15,Hard 37,Master 49,Unlimited 69,
|
||||||
|
100627,dangan,弾丸と星空,HAKKYOU-KUN,Original,Easy N/A,Standard 28,Hard 58,Master 81,Unlimited N/A,
|
||||||
|
100628,impact,Impact,Tatsh,Original,Easy 20,Standard 24,Hard 60,Master 72,Unlimited 90,
|
||||||
|
100629,lightm,Light My Fire,KOTOKO,J-Pop,Easy 11,Standard 26,Hard 33,Master 54,Unlimited 71,
|
||||||
|
100630,miiroo,海色,AKINO from bless4,J-Pop,Easy 11,Standard 22,Hard 39,Master 58,Unlimited 68,
|
||||||
|
100631,voiceo,Voice Of House,DOT96,Original,Easy N/A,Standard 18,Hard 34,Master 58,Unlimited 59,
|
||||||
|
100632,cosmol,Cosmology,RIC,Original,Easy 25,Standard 36,Hard 64,Master 87,Unlimited N/A,
|
||||||
|
100633,vividd,ViViD,May'n,J-Pop,Easy 9,Standard 16,Hard 35,Master 55,Unlimited 65,
|
||||||
|
100634,splash,SPLASH,MAYA AKAI,Original,Easy N/A,Standard 26,Hard 50,Master 71,Unlimited N/A,
|
||||||
|
100635,donuth,ドーナツホール,ハチ,Vocaloid,Easy 11,Standard 22,Hard 40,Master 54,Unlimited 80,
|
||||||
|
100636,senbon,千本桜,和楽器バンド,Vocaloid,Easy 12,Standard 20,Hard 28,Master 54,Unlimited 74,
|
||||||
|
100637,kmtyju,君と野獣,バンドハラスメント,J-Pop,Easy 12,Standard 24,Hard 31,Master 57,Unlimited 74,
|
||||||
|
100638,fronti,NEXT FRONTIER,NAOKI,Original,Easy 13,Standard 48,Hard 65,Master 82,Unlimited N/A,
|
||||||
|
100639,nueraa,Nu Era,SPARKER,Original,Easy N/A,Standard 22,Hard 43,Master 75,Unlimited 53,
|
||||||
|
100640,childe,CHiLD -error-,MY FIRST STORY,J-Pop,Easy 4,Standard 9,Hard 24,Master 34,Unlimited 56,
|
||||||
|
100641,dazzli2,DAZZLING♡SEASON (Darwin Remix),jun,Original,Easy 19,Standard 35,Hard 60,Master 82,Unlimited N/A,
|
||||||
|
100642,perfec,Perfectionism,高橋渉 feat.2d6,Original,Easy N/A,Standard 39,Hard 64,Master 78,Unlimited N/A,
|
||||||
|
100643,flower,Flowerwall,米津玄師,J-Pop,Easy 6,Standard 7,Hard 20,Master 40,Unlimited 65,
|
||||||
|
100644,frgmnt,Frgmnts,Nyolfen,Original,Easy 10,Standard 33,Hard 63,Master 74,Unlimited 65,
|
||||||
|
100645,headph,HEADPHONE PARTY,A-One,Original,Easy N/A,Standard 24,Hard 32,Master 52,Unlimited N/A,
|
||||||
|
100646,crsang,Cross+Angel,Tatsh feat. 彩音,Original,Easy 13,Standard 27,Hard 53,Master 67,Unlimited N/A,
|
||||||
|
100647,musicr4,Accept,sushi feat.とよだま,Original,Easy 12,Standard 19,Hard 32,Master 58,Unlimited N/A,
|
||||||
|
100648,imaxim,A×E×U×G -act.1-,190Cb,Original,Easy N/A,Standard 44,Hard 69,Master 90,Unlimited 87,
|
||||||
|
100649,azitat2,Azitate (Prologue Edition),void,Original,Easy 8,Standard 23,Hard 52,Master 66,Unlimited N/A,
|
||||||
|
100650,dynami,DYNAMITE SENSATION,NAOKI,Original,Easy 11,Standard 26,Hard 54,Master 68,Unlimited N/A,
|
||||||
|
100651,incave,Into the Cave,Jerico,Original,Easy N/A,Standard 22,Hard 44,Master 76,Unlimited 78,
|
||||||
|
100652,aktuki,AKATSUKI,NAOKI underground,Original,Easy 10,Standard 26,Hard 58,Master 84,Unlimited N/A,
|
||||||
|
100653,kindof,Wonderful,Fraz,Original,Easy N/A,Standard 14,Hard 29,Master 48,Unlimited N/A,
|
||||||
|
100654,mikaku,未確認XX生命体,民安★ROCK,Original,Easy N/A,Standard 19,Hard 31,Master 54,Unlimited N/A,
|
||||||
|
100655,strang,ストレンジ・ディーヴァ,麹町養蚕館,Original,Easy N/A,Standard 12,Hard 28,Master 55,Unlimited N/A,
|
||||||
|
100656,hesper,Hesperides,xi,Original,Easy N/A,Standard 36,Hard 61,Master 92,Unlimited 93,
|
||||||
|
100657,breaka,Break a spell,川田まみ,J-Pop,Easy 7,Standard 15,Hard 31,Master 45,Unlimited 68,
|
||||||
|
100658,myname,When You Call My Name,Beat Envy,Original,Easy N/A,Standard 6,Hard 14,Master 30,Unlimited 57,
|
||||||
|
100659,amaiko,甘い言葉,Kenichi Chiba feat. EVO+,Original,Easy N/A,Standard 15,Hard 37,Master 60,Unlimited N/A,
|
||||||
|
100660,reseed2,Reseed,quick master,Original,Easy N/A,Standard 22,Hard 47,Master 63,Unlimited N/A,
|
||||||
|
100661,kingst,KING STUN,JUPITRIS,Original,Easy 12,Standard 38,Hard 63,Master 74,Unlimited N/A,
|
||||||
|
100662,ramram,Break Your World,RAM,Original,Easy N/A,Standard 23,Hard 34,Master 67,Unlimited N/A,
|
||||||
|
100663,murasa,Murasame,Ryunosuke Kudo,Original,Easy N/A,Standard 28,Hard 41,Master 76,Unlimited N/A,
|
||||||
|
100664,happyd,Happy Deathday,ANOTHER STORY,Original,Easy 18,Standard 22,Hard 41,Master 73,Unlimited 79,
|
||||||
|
100665,izimed,イジメ、ダメ、ゼッタイ,BABYMETAL,J-Pop,Easy 9,Standard 19,Hard 39,Master 69,Unlimited 77,
|
||||||
|
100666,wastel,Wasteland,James Taplin,Original,Easy N/A,Standard 4,Hard 12,Master 23,Unlimited 40,
|
||||||
|
100667,assign,Assign,MASAYASU,Original,Easy N/A,Standard 26,Hard 43,Master 61,Unlimited 62,
|
||||||
|
100668,jahaci,Jahacid,DJ SODEYAMA,Original,Easy N/A,Standard 17,Hard 29,Master 59,Unlimited N/A,
|
||||||
|
100669,hisuii,Hisui,stereoberry,Original,Easy N/A,Standard 22,Hard 47,Master 70,Unlimited N/A,
|
||||||
|
100670,godkno,God knows...,涼宮ハルヒ(C.V.平野綾),J-Pop,Easy 6,Standard 10,Hard 26,Master 45,Unlimited 64,
|
||||||
|
100671,roadof,Road of Resistance,BABYMETAL,J-Pop,Easy 7,Standard 15,Hard 36,Master 50,Unlimited 75,
|
||||||
|
100672,rokuch,六兆年と一夜物語,和楽器バンド,J-Pop + Vocaloid,Easy 11,Standard 21,Hard 35,Master 62,Unlimited 81,
|
||||||
|
100673,valent,いつか王子様が (Remix Ver.),,Original,Easy 10,Standard 27,Hard 33,Master 59,Unlimited 77,
|
||||||
|
100674,unfini,→unfinished→,KOTOKO,J-Pop,Easy 8,Standard 16,Hard 32,Master 50,Unlimited 71,
|
||||||
|
100675,auflcb2,some day -see you again-,NAOKI,Original,Easy 10,Standard 22,Hard 37,Master 75,Unlimited N/A,
|
||||||
|
100676,burnin,Burning Inside,Nhato,Original,Easy 15,Standard 18,Hard 28,Master 60,Unlimited 85,
|
||||||
|
100677,sphere,Hypersphere,Dubscribe,Original,Easy N/A,Standard 20,Hard 38,Master 73,Unlimited N/A,
|
||||||
|
100678,dropou,D.O.B.,野水いおり,J-Pop,Easy 14,Standard 17,Hard 31,Master 46,Unlimited 69,
|
||||||
|
100679,xencou,X-encounter,黒崎真音,J-Pop,Easy 8,Standard 20,Hard 32,Master 52,Unlimited 60,
|
||||||
|
100680,killyk,killy killy JOKER,分島花音,J-Pop,Easy 6,Standard 13,Hard 42,Master 63,Unlimited 76,
|
||||||
|
100681,missil,the Last Missile Man,adHoc World,Original,Easy N/A,Standard 16,Hard 38,Master 59,Unlimited N/A,
|
||||||
|
100682,burstt,Burst The Gravity,ALTIMA,J-Pop,Easy 7,Standard 12,Hard 25,Master 46,Unlimited 63,
|
||||||
|
100683,musicr2,My Recklessness,Kagerou,Original,Easy 12,Standard 22,Hard 33,Master 58,Unlimited N/A,
|
||||||
|
100684,isingl,Isinglass,Voltex,Original,Easy 12,Standard 25,Hard 44,Master 80,Unlimited N/A,
|
||||||
|
100685,lvless,Loveless,YOSA,Original,Easy N/A,Standard 23,Hard 38,Master 60,Unlimited N/A,
|
||||||
|
100686,sapphi,Sapphire,voltex,Original,Easy N/A,Standard 29,Hard 44,Master 81,Unlimited N/A,
|
||||||
|
100687,musicr3,Climaxxx Party -musicるTV・ミリオン連発音楽作家塾第7弾-,Kyota. feat.とよだま&れい,Original,Easy 12,Standard 19,Hard 32,Master 58,Unlimited 72,
|
||||||
|
100688,deeout,Deep Outside,Seiho,Original,Easy N/A,Standard 18,Hard 34,Master 63,Unlimited 81,
|
||||||
|
100689,sugars,シュガーソングとビターステップ,UNISON SQUARE GARDEN,J-Pop,Easy 6,Standard 17,Hard 30,Master 42,Unlimited 66,
|
||||||
|
100690,mercur,MERCURY ,E.Z.M,Original,Easy N/A,Standard 14,Hard 35,Master 66,Unlimited N/A,
|
||||||
|
100691,zizizi,Z[i],Cybermiso,Original,Easy N/A,Standard 30,Hard 57,Master 88,Unlimited 96,
|
||||||
|
100692,wegooo,WE GO,BREAKERZ,J-Pop,Easy 10,Standard 18,Hard 34,Master 54,Unlimited 68,
|
||||||
|
100693,alonee,ALONE,MY FIRST STORY,J-Pop,Easy 5,Standard 11,Hard 21,Master 36,Unlimited 48,
|
||||||
|
100694,nuheat,Nu Heat,Paisley Parks,Original,Easy N/A,Standard 29,Hard 44,Master 65,Unlimited 85,
|
||||||
|
100695,granro,メモリーズ,GRANRODEO,J-Pop,Easy 8,Standard 15,Hard 28,Master 43,Unlimited 60,
|
||||||
|
100696,sister,sister's noise,fripSide,J-Pop,Easy 7,Standard 10,Hard 27,Master 46,Unlimited 63,
|
||||||
|
100697,lotusl,Lotus Love,Maozon,Original,Easy N/A,Standard 20,Hard 36,Master 64,Unlimited N/A,
|
||||||
|
100698,yukari,YUKARI,Ocelot,Original,Easy N/A,Standard 31,Hard 50,Master 76,Unlimited 84,
|
||||||
|
100699,flawli,フローライト,米津玄師,J-Pop,Easy 8,Standard 17,Hard 30,Master 40,Unlimited 59,
|
||||||
|
100700,nightf,NIGHT FEELIN',マセラティ渚,Original,Easy N/A,Standard 15,Hard 28,Master 46,Unlimited 71,
|
||||||
|
100701,random,シャッフルセレクト,シャッフルセレクト,Original,Easy N/A,Standard N/A,Hard N/A,Master N/A,Unlimited N/A,
|
||||||
|
100702,wiwwtw,What Is Wrong With The World,SADA,Original,Easy N/A,Standard 26,Hard 38,Master 62,Unlimited N/A,
|
||||||
|
100703,inneru,Inner Urge,上坂すみれ,Original,Easy 9,Standard 22,Hard 36,Master 48,Unlimited 67,
|
||||||
|
100704,taishi,Otherside,Taishi,Original,Easy N/A,Standard 19,Hard 35,Master 58,Unlimited N/A,
|
||||||
|
100705,daysss,Days,Kent Alexander,Original,Easy N/A,Standard 38,Hard 59,Master 81,Unlimited 81,
|
||||||
|
100706,bokuwa,僕は君のアジテーターじゃない feat.Neru,焚吐,J-Pop,Easy 16,Standard 23,Hard 34,Master 55,Unlimited 69,
|
||||||
|
100707,showww,掌 -show-,喜多村英梨,Original,Easy 15,Standard 18,Hard 35,Master 51,Unlimited 79,
|
||||||
|
100708,nevers,Never Sleep Again,PassCode,J-Pop,Easy 15,Standard 26,Hard 32,Master 65,Unlimited 75,
|
||||||
|
100709,bleeze,BLEEZE,GLAY,J-Pop,Easy 9,Standard 16,Hard 31,Master 47,Unlimited 62,
|
||||||
|
100710,dreami,DREAMIN' OF YOU feat.コッテル,Arts Of Collective,Original,Easy N/A,Standard 14,Hard 37,Master 65,Unlimited N/A,
|
||||||
|
100711,allune,All U Need,MesoPhunk,Pick-Up (New + Revival),Easy N/A,Standard 14,Hard 35,Master 71,Unlimited N/A,
|
||||||
|
100712,always,Always Thinking Of You,Sketchout,Pick-Up (New + Revival),Easy N/A,Standard 13,Hard 27,Master 49,Unlimited N/A,
|
||||||
|
100713,anomie2,Anomie (Axiom Style),D-Fener,Pick-Up (New + Revival),Easy N/A,Standard 16,Hard 43,Master 84,Unlimited N/A,
|
||||||
|
100714,aquali2,Aqualight (Remix Ver.),MAYA AKAI,Pick-Up (New + Revival),Easy N/A,Standard 22,Hard 43,Master 60,Unlimited 81,
|
||||||
|
100715,astaro,ASTAROTH,JUPITRIS,Pick-Up (New + Revival),Easy N/A,Standard 23,Hard 40,Master 74,Unlimited N/A,
|
||||||
|
100716,bassan,BASS ANTICS,Mitomoro,Pick-Up (New + Revival),Easy N/A,Standard 20,Hard 32,Master 66,Unlimited N/A,
|
||||||
|
100717,zonzon2,Bi-Zon Zon Zombi (More Zombies Ver.),MC Natsack,Pick-Up (New + Revival),Easy N/A,Standard 13,Hard 27,Master 68,Unlimited 75,
|
||||||
|
100718,bouled,boule de berlin,JTTR,Pick-Up (New + Revival),Easy N/A,Standard 19,Hard 30,Master 57,Unlimited N/A,
|
||||||
|
100719,brandn,BRAND NEW,Headphone-Tokyo(star)(star) feat.カヒーナ,Pick-Up (New + Revival),Easy N/A,Standard 9,Hard 39,Master 66,Unlimited 72,
|
||||||
|
100720,bravee,BRAVE,Ryuno,Pick-Up (New + Revival),Easy N/A,Standard 35,Hard 60,Master 82,Unlimited N/A,
|
||||||
|
100721,breakd2,Break down (2nd Edition),GARNiDELiA,Pick-Up (New + Revival),Easy N/A,Standard 34,Hard 64,Master 74,Unlimited N/A,
|
||||||
|
100722,buffet,Buffet survivor,Yamajet feat. Cathy & TEA,Pick-Up (New + Revival),Easy N/A,Standard 38,Hard 55,Master 68,Unlimited N/A,
|
||||||
|
100723,buzzke,BUZZ Ketos,フラット3rd,Pick-Up (New + Revival),Easy N/A,Standard 18,Hard 33,Master 58,Unlimited 77,
|
||||||
|
100724,cashhh,Cash!,Nor,Pick-Up (New + Revival),Easy N/A,Standard 19,Hard 25,Master 64,Unlimited N/A,
|
||||||
|
100725,cloudb,Cloudburst,Relect,Pick-Up (New + Revival),Easy N/A,Standard 37,Hard 66,Master 74,Unlimited N/A,
|
||||||
|
100726,clouds,cloudstepping,Ryuno,Pick-Up (New + Revival),Easy N/A,Standard 13,Hard 25,Master 47,Unlimited N/A,
|
||||||
|
100727,codepa,Code Paradiso,Himmel,Pick-Up (New + Revival),Easy N/A,Standard 29,Hard 55,Master 70,Unlimited N/A,
|
||||||
|
100728,comear,Come Around,MesoPhunk,Pick-Up (New + Revival),Easy N/A,Standard 38,Hard 56,Master 83,Unlimited N/A,
|
||||||
|
100729,crysta,Crystal Ribbon,Cosine,Pick-Up (New + Revival),Easy N/A,Standard 37,Hard 56,Master 81,Unlimited N/A,
|
||||||
|
100730,curseo,Curse of Doll,KO3,Pick-Up (New + Revival),Easy N/A,Standard 22,Hard 36,Master 74,Unlimited N/A,
|
||||||
|
100731,datami,data mining,voia,Pick-Up (New + Revival),Easy N/A,Standard 18,Hard 36,Master 66,Unlimited N/A,
|
||||||
|
100732,defaul,default affinity,JTTR,Pick-Up (New + Revival),Easy N/A,Standard 21,Hard 33,Master 48,Unlimited N/A,
|
||||||
|
100733,design2,Designed World (Remix ver.),Alinut,Pick-Up (New + Revival),Easy N/A,Standard 25,Hard 43,Master 68,Unlimited N/A,
|
||||||
|
100734,diamon,DIAMOND SKIN,GLAY,Pick-Up (New + Revival),Easy N/A,Standard 10,Hard 26,Master 33,Unlimited N/A,
|
||||||
|
100735,dispel,dispel,Endorfin.,Pick-Up (New + Revival),Easy N/A,Standard 28,Hard 48,Master 80,Unlimited N/A,
|
||||||
|
100736,distan,Distantmemory,村瀬悠太,Pick-Up (New + Revival),Easy N/A,Standard 20,Hard 30,Master 68,Unlimited N/A,
|
||||||
|
100737,dokibl,Doki Blaster,VOIA,Pick-Up (New + Revival),Easy N/A,Standard 15,Hard 23,Master 67,Unlimited N/A,
|
||||||
|
100738,dontwa,Don't Walk Away,Sarah-Jane,Pick-Up (New + Revival),Easy N/A,Standard 13,Hard 34,Master 69,Unlimited N/A,
|
||||||
|
100739,drgirl,Dreaming Girl,Nor,Pick-Up (New + Revival),Easy N/A,Standard 19,Hard 35,Master 54,Unlimited 73,
|
||||||
|
100740,eterna,Eternally,GLAY,Pick-Up (New + Revival),Easy N/A,Standard 12,Hard 21,Master 39,Unlimited N/A,
|
||||||
|
100741,everkr,everKrack,GLAY,Pick-Up (New + Revival),Easy N/A,Standard 18,Hard 29,Master 41,Unlimited N/A,
|
||||||
|
100742,everwh,EverWhite,satella,Pick-Up (New + Revival),Easy N/A,Standard 20,Hard 31,Master 58,Unlimited N/A,
|
||||||
|
100743,farthe,FarthestEnd,Sakuzyo,Pick-Up (New + Revival),Easy N/A,Standard 30,Hard 56,Master 78,Unlimited 87,
|
||||||
|
100744,filame,Filament Flow,Endorfin.,Pick-Up (New + Revival),Easy N/A,Standard 23,Hard 38,Master 63,Unlimited N/A,
|
||||||
|
100745,flameu2,Flame Up (Remix Ver.),Inu Machine,Pick-Up (New + Revival),Easy N/A,Standard 17,Hard 24,Master 59,Unlimited N/A,
|
||||||
|
100746,freeee,Free,千π,Pick-Up (New + Revival),Easy N/A,Standard 19,Hard 39,Master 69,Unlimited N/A,
|
||||||
|
100747,funkyb2,FUNKYBABY EVOLUTION,Yamajet,Pick-Up (New + Revival),Easy N/A,Standard 21,Hard 34,Master 56,Unlimited N/A,
|
||||||
|
100748,granda2,Grand Arc (Club Remix),Tosh,Pick-Up (New + Revival),Easy N/A,Standard 24,Hard 41,Master 73,Unlimited 83,
|
||||||
|
100749,hsphsp,H.S.P (Hard Style Party),Ravine & Tom Budin,Pick-Up (New + Revival),Easy N/A,Standard 12,Hard 25,Master 69,Unlimited N/A,
|
||||||
|
100750,halluc,Hallucination XXX,t+pazolite,Pick-Up (New + Revival),Easy N/A,Standard 40,Hard 52,Master 87,Unlimited N/A,
|
||||||
|
100751,indigo,Indigo Isle,Syntax Error,Pick-Up (New + Revival),Easy N/A,Standard 17,Hard 33,Master 50,Unlimited 75,
|
||||||
|
100752,inters,Interstellar Plazma,KO3,Pick-Up (New + Revival),Easy N/A,Standard 25,Hard 42,Master 77,Unlimited N/A,
|
||||||
|
100753,incave2,Into the Cave (Another Edit),Jerico,Pick-Up (New + Revival),Easy N/A,Standard 31,Hard 57,Master 88,Unlimited N/A,
|
||||||
|
100754,ioniza,IONIZATION,llliiillliiilll,Pick-Up (New + Revival),Easy N/A,Standard 17,Hard 34,Master 70,Unlimited 85,
|
||||||
|
100755,guilty,JUSTICE [from] GUILTY,GLAY,Pick-Up (New + Revival),Easy N/A,Standard 15,Hard 28,Master 50,Unlimited N/A,
|
||||||
|
100756,keraun,Keraunos,Xiphoid Sphere (xi + siromaru),Pick-Up (New + Revival),Easy N/A,Standard 25,Hard 52,Master 79,Unlimited N/A,
|
||||||
|
100757,landin2,Landing on the moon (Instrumental Version),SIMON,Pick-Up (New + Revival),Easy N/A,Standard 20,Hard 34,Master 59,Unlimited 66,
|
||||||
|
100758,videog,Life In A Video Game,Bentobox,Pick-Up (New + Revival),Easy N/A,Standard 21,Hard 37,Master 62,Unlimited N/A,
|
||||||
|
100759,loseyo,Lose Your Mind,Vau Boy,Pick-Up (New + Revival),Easy N/A,Standard 20,Hard 30,Master 71,Unlimited N/A,
|
||||||
|
100760,machin,Machine,Sparky,Pick-Up (New + Revival),Easy N/A,Standard 12,Hard 28,Master 72,Unlimited N/A,
|
||||||
|
100761,makeit,Make It Fresh EDM ver.,HighLux,Pick-Up (New + Revival),Easy N/A,Standard 11,Hard 24,Master 48,Unlimited N/A,
|
||||||
|
100762,daydre,Mechanized Daydream,s-don,Pick-Up (New + Revival),Easy N/A,Standard 19,Hard 36,Master 80,Unlimited N/A,
|
||||||
|
100763,metron,Metro Night,ginkiha,Pick-Up (New + Revival),Easy N/A,Standard 20,Hard 44,Master 71,Unlimited N/A,
|
||||||
|
100764,milkyw,Milky Way Trip,Nor,Pick-Up (New + Revival),Easy N/A,Standard 22,Hard 31,Master 60,Unlimited N/A,
|
||||||
|
100766,nayuta,nayuta,happy machine,Pick-Up (New + Revival),Easy N/A,Standard 17,Hard 37,Master 68,Unlimited N/A,
|
||||||
|
100767,nightm,nightmares,Seeds of the Upcoming Infection,Pick-Up (New + Revival),Easy N/A,Standard 20,Hard 49,Master 73,Unlimited N/A,
|
||||||
|
100768,otherw,Other World,XIO,Pick-Up (New + Revival),Easy N/A,Standard 23,Hard 41,Master 76,Unlimited N/A,
|
||||||
|
100769,overth,Over The Blue (Breaking Through),Fracus & Darwin Feat. Jenna,Pick-Up (New + Revival),Easy N/A,Standard 33,Hard 57,Master 82,Unlimited N/A,
|
||||||
|
100770,uuuuuu,Phoenix,U,Pick-Up (New + Revival),Easy N/A,Standard 23,Hard 37,Master 74,Unlimited N/A,
|
||||||
|
100771,rainin,Raining Again feat. Bea Aria,Sanxion,Pick-Up (New + Revival),Easy N/A,Standard 16,Hard 41,Master 69,Unlimited N/A,
|
||||||
|
100772,raisey,Raise Your Handz!,KO3 & Relect,Pick-Up (New + Revival),Easy N/A,Standard 23,Hard 55,Master 75,Unlimited N/A,
|
||||||
|
100773,resona,Resonance,RAM,Pick-Up (New + Revival),Easy N/A,Standard 17,Hard 32,Master 64,Unlimited N/A,
|
||||||
|
100774,reuniv,Reuniverse,Headphone-Tokyo(star)(star) feat.カヒーナ,Pick-Up (New + Revival),Easy N/A,Standard 14,Hard 23,Master 41,Unlimited N/A,
|
||||||
|
100775,rhythm,RHYTHM GAME MACHINE,ginkiha,Pick-Up (New + Revival),Easy N/A,Standard 37,Hard 56,Master 78,Unlimited N/A,
|
||||||
|
100776,rushhh,Rush,TANUKI,Pick-Up (New + Revival),Easy N/A,Standard 25,Hard 37,Master 75,Unlimited N/A,
|
||||||
|
100777,steeee,S.T.E,Tatsh,Pick-Up (New + Revival),Easy N/A,Standard 30,Hard 58,Master 87,Unlimited N/A,
|
||||||
|
100778,sangey,Sangeyasya,NNNNNNNNNN,Pick-Up (New + Revival),Easy N/A,Standard 27,Hard 47,Master 85,Unlimited N/A,
|
||||||
|
100779,senpai,Senpai Slam,千π,Pick-Up (New + Revival),Easy N/A,Standard 38,Hard 54,Master 77,Unlimited N/A,
|
||||||
|
100780,sestea,Sestea,Feryquitous,Pick-Up (New + Revival),Easy N/A,Standard 27,Hard 47,Master 76,Unlimited N/A,
|
||||||
|
100781,silver,Silverd,Feryquitous,Pick-Up (New + Revival),Easy N/A,Standard 28,Hard 40,Master 69,Unlimited N/A,
|
||||||
|
100782,sodama,Soda Machine,Syntax Error,Pick-Up (New + Revival),Easy N/A,Standard 20,Hard 40,Master 65,Unlimited N/A,
|
||||||
|
100783,stardu,STARDUST (game edit),MINIKOMA★,Pick-Up (New + Revival),Easy N/A,Standard 19,Hard 33,Master 64,Unlimited N/A,
|
||||||
|
100784,starti,starting station,happy machine,Pick-Up (New + Revival),Easy N/A,Standard 17,Hard 31,Master 54,Unlimited 70,
|
||||||
|
100785,sunday,SUNDAY リベンジ,HAPPY SUNDAY,Pick-Up (New + Revival),Easy N/A,Standard 18,Hard 29,Master 46,Unlimited 67,
|
||||||
|
100786,sundro2,Sundrop (Remix ver.),Yamajet,Pick-Up (New + Revival),Easy N/A,Standard 30,Hard 48,Master 79,Unlimited 82,
|
||||||
|
100787,sunnyd,Sunny day,センラ×蒼炎P,Pick-Up (New + Revival),Easy N/A,Standard 23,Hard 38,Master 59,Unlimited N/A,
|
||||||
|
100788,superl,SuperLuminalGirl Rebirth,Yamajet feat. 小宮真央,Pick-Up (New + Revival),Easy N/A,Standard 15,Hard 32,Master 59,Unlimited N/A,
|
||||||
|
100789,switch,SW!TCH,千π & MesoPhunk,Pick-Up (New + Revival),Easy N/A,Standard 16,Hard 35,Master 69,Unlimited N/A,
|
||||||
|
100790,theepi2,The Epic -Introduction-,Cranky,Pick-Up (New + Revival),Easy N/A,Standard 22,Hard 37,Master 65,Unlimited N/A,
|
||||||
|
100791,epipha,The Epiphany of Hardcore,SOTUI,Pick-Up (New + Revival),Easy N/A,Standard 15,Hard 30,Master 70,Unlimited N/A,
|
||||||
|
100792,thekin,The King of Pirates,RiraN,Pick-Up (New + Revival),Easy N/A,Standard 22,Hard 52,Master 75,Unlimited N/A,
|
||||||
|
100793,timele,Timeless encode,Vice Principal,Pick-Up (New + Revival),Easy N/A,Standard 16,Hard 33,Master 72,Unlimited N/A,
|
||||||
|
100794,tokyoo,tokyo,Headphone-Tokyo(star)(star) feat.nayuta,Pick-Up (New + Revival),Easy N/A,Standard 15,Hard 33,Master 71,Unlimited N/A,
|
||||||
|
100795,toooma,Tooo Many,S3RL,Pick-Up (New + Revival),Easy N/A,Standard 30,Hard 51,Master 77,Unlimited N/A,
|
||||||
|
100796,toucho2,Touch Of Gold (Bongo Mango Remix),Togo Project feat. Frances Maya,Pick-Up (New + Revival),Easy N/A,Standard 17,Hard 32,Master 52,Unlimited 78,
|
||||||
|
100797,tayuta,tΔyutΔi,ミフメイ,Pick-Up (New + Revival),Easy N/A,Standard 26,Hard 35,Master 72,Unlimited N/A,
|
||||||
|
100798,ultrix,ULTRiX,sky_delta,Pick-Up (New + Revival),Easy N/A,Standard 27,Hard 45,Master 76,Unlimited N/A,
|
||||||
|
100799,underw,Underworld,ANOTHER STORY,Pick-Up (New + Revival),Easy N/A,Standard 29,Hard 46,Master 69,Unlimited 86,
|
||||||
|
100800,virtua,Virtual Reality Controller,フラット3rd,Pick-Up (New + Revival),Easy N/A,Standard 15,Hard 35,Master 63,Unlimited N/A,
|
||||||
|
100801,voiceo2,VOICE OF HOUSE (96TH RETROMAN REMIX),DOT96,Pick-Up (New + Revival),Easy N/A,Standard 14,Hard 38,Master 67,Unlimited N/A,
|
||||||
|
100802,wannab2,Wanna Be Your Special (Remix ver.),Shoichiro Hirata feat. SUIMI,Pick-Up (New + Revival),Easy N/A,Standard 26,Hard 41,Master 69,Unlimited N/A,
|
||||||
|
100803,wiwwtw2,What Is Wrong With The World (Cross Edit),SADA,Pick-Up (New + Revival),Easy N/A,Standard 20,Hard 43,Master 67,Unlimited 72,
|
||||||
|
100804,wingso,Wings of Twilight,sky_delta,Pick-Up (New + Revival),Easy N/A,Standard 20,Hard 53,Master 71,Unlimited N/A,
|
||||||
|
100805,winter,Winter again,GLAY,Pick-Up (New + Revival),Easy N/A,Standard 14,Hard 24,Master 41,Unlimited N/A,
|
||||||
|
100806,iineee,いいね!,BABYMETAL,Pick-Up (New + Revival),Easy N/A,Standard 21,Hard 40,Master 81,Unlimited N/A,
|
||||||
|
100807,illumi,イルミナレガロ,Headphone-Tokyo(star)(star) feat.MiLO,Pick-Up (New + Revival),Easy N/A,Standard 10,Hard 25,Master 46,Unlimited 63,
|
||||||
|
100808,yellll,エール,FullMooN,Pick-Up (New + Revival),Easy N/A,Standard 8,Hard 17,Master 52,Unlimited N/A,
|
||||||
|
100809,eschat,エスカトロジィ,MozSound,Pick-Up (New + Revival),Easy N/A,Standard 36,Hard 57,Master 77,Unlimited N/A,
|
||||||
|
100810,counte,カウンターストップ,フラット3rd,Pick-Up (New + Revival),Easy N/A,Standard 29,Hard 34,Master 71,Unlimited N/A,
|
||||||
|
100811,gimcho,ギミチョコ!!,BABYMETAL,Pick-Up (New + Revival),Easy N/A,Standard 18,Hard 39,Master 70,Unlimited N/A,
|
||||||
|
100812,surviv,サバイバル,GLAY,Pick-Up (New + Revival),Easy N/A,Standard 24,Hard 40,Master 65,Unlimited N/A,
|
||||||
|
100814,turkis3,トルコ行進曲 (Short Remix),,Pick-Up (New + Revival),Easy N/A,Standard 6,Hard 20,Master 48,Unlimited N/A,
|
||||||
|
100815,picora2,ピコラセテ (Instrumental Ver.),TORIENA,Pick-Up (New + Revival),Easy N/A,Standard 28,Hard 53,Master 80,Unlimited N/A,
|
||||||
|
100816,fortis,フォルテシモ,らむだーじゃん,Pick-Up (New + Revival),Easy N/A,Standard 20,Hard 37,Master 53,Unlimited N/A,
|
||||||
|
100817,hedban,ヘドバンギャー!!,BABYMETAL,Pick-Up (New + Revival),Easy N/A,Standard 16,Hard 43,Master 66,Unlimited N/A,
|
||||||
|
100818,megitu,メギツネ,BABYMETAL,Pick-Up (New + Revival),Easy N/A,Standard 15,Hard 30,Master 49,Unlimited N/A,
|
||||||
|
100819,rockma,ロックマン (CUTMAN STAGE),Remixed by てつ×ねこ,Pick-Up (New + Revival),Easy N/A,Standard 27,Hard 48,Master 73,Unlimited N/A,
|
||||||
|
100820,kounen2,光年(konen)-Remix Ver.-,小野秀幸,Pick-Up (New + Revival),Easy N/A,Standard 21,Hard 43,Master 73,Unlimited N/A,
|
||||||
|
100821,saisyu,最終回STORY,MY FIRST STORY,Pick-Up (New + Revival),Easy N/A,Standard 18,Hard 36,Master 56,Unlimited N/A,
|
||||||
|
100822,yuukan,勇敢 i tout,kamejack,Pick-Up (New + Revival),Easy N/A,Standard 22,Hard 33,Master 78,Unlimited N/A,
|
||||||
|
100823,modern,彼女の“Modern…” CROSS×BEATS Remix,GLAY,Pick-Up (New + Revival),Easy N/A,Standard 20,Hard 32,Master 56,Unlimited N/A,
|
||||||
|
100824,miraie,未来へのプレリュード,カヒーナムジカ,Pick-Up (New + Revival),Easy N/A,Standard 21,Hard 35,Master 66,Unlimited N/A,
|
||||||
|
100825,ranfes,狂乱セレブレーション,Yamajet,Pick-Up (New + Revival),Easy N/A,Standard 20,Hard 42,Master 65,Unlimited N/A,
|
||||||
|
100826,nemure,眠れない歌,iru,Pick-Up (New + Revival),Easy N/A,Standard 15,Hard 38,Master 67,Unlimited 76,
|
||||||
|
100827,yuwaku,誘惑,GLAY,Pick-Up (New + Revival),Easy N/A,Standard 15,Hard 26,Master 43,Unlimited N/A,
|
||||||
|
100828,dontst,Don't Stop The Music feat.森高千里,tofubeats,Pick-Up (New + Revival),Easy N/A,Standard 15,Hard 32,Master 56,Unlimited 70,
|
||||||
|
100829,mottai,もったいないとらんど,きゃりーぱみゅぱみゅ,Pick-Up (New + Revival),Easy N/A,Standard 10,Hard 26,Master 36,Unlimited N/A,
|
||||||
|
100830,slysly,SLY,RIP SLYME,Pick-Up (New + Revival),Easy N/A,Standard 10,Hard 33,Master 58,Unlimited N/A,
|
||||||
|
100831,lookam,(Where's)THE SILENT MAJORITY?,高橋優,Pick-Up (New + Revival),Easy N/A,Standard 17,Hard 34,Master 67,Unlimited N/A,
|
||||||
|
100832,feverr,フィーバー,パスピエ,Pick-Up (New + Revival),Easy N/A,Standard 28,Hard 48,Master 68,Unlimited N/A,
|
||||||
|
100833,fashio,ファッションモンスター,きゃりーぱみゅぱみゅ,Pick-Up (New + Revival),Easy N/A,Standard 8,Hard 24,Master 39,Unlimited N/A,
|
||||||
|
100834,hagito,「ハギとこ!」のテーマ,ハギー,Pick-Up (New + Revival),Easy N/A,Standard 12,Hard 26,Master 50,Unlimited N/A,
|
||||||
|
100835,invade,インベーダーインベーダー,きゃりーぱみゅぱみゅ,Pick-Up (New + Revival),Easy N/A,Standard 10,Hard 28,Master 47,Unlimited N/A,
|
||||||
|
100836,ainoch,愛の地球祭,チームしゃちほこ,Pick-Up (New + Revival),Easy N/A,Standard 17,Hard 40,Master 59,Unlimited N/A,
|
||||||
|
100837,nakama,仲間を探したい,神聖かまってちゃん,Pick-Up (New + Revival),Easy N/A,Standard 14,Hard 32,Master 53,Unlimited N/A,
|
||||||
|
100838,ninjar,にんじゃりばんばん,きゃりーぱみゅぱみゅ,Pick-Up (New + Revival),Easy N/A,Standard 8,Hard 23,Master 41,Unlimited 65,
|
||||||
|
100839,parall,パラレルスペック,ゲスの極み乙女。,Pick-Up (New + Revival),Easy N/A,Standard 14,Hard 35,Master 61,Unlimited N/A,
|
||||||
|
100840,yukifu,雪降る夜にキスして,バンドじゃないもん!,Pick-Up (New + Revival),Easy N/A,Standard 13,Hard 29,Master 51,Unlimited N/A,
|
||||||
|
100841,furiso,ふりそでーしょん,きゃりーぱみゅぱみゅ,Pick-Up (New + Revival),Easy N/A,Standard 12,Hard 24,Master 44,Unlimited 74,
|
||||||
|
100842,honeyj,HONEY♡SUNRiSE ~jun Side~,jun with Aimee,Original,Easy 24,Standard 32,Hard 63,Master 88,Unlimited 93,
|
||||||
|
100843,emeraj,EMERALD♡KISS ~jun Side~,jun with Aimee,Original,Easy 19,Standard 30,Hard 53,Master 85,Unlimited N/A,
|
||||||
|
100844,dazzlo,DAZZLING♡SEASON (Original Side),jun,Original,Easy 16,Standard 35,Hard 60,Master 80,Unlimited 90,
|
||||||
|
100844,shares,SHARE SONG,SHARE SONG,Original,Easy N/A,Standard N/A,Hard N/A,Master N/A,Unlimited N/A,
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
|
|
||||||
from core.data import Data
|
from core.data import Data
|
||||||
from core.config import CoreConfig
|
from core.config import CoreConfig
|
||||||
from titles.cxb.schema import CxbProfileData, CxbScoreData, CxbItemData, CxbStaticData
|
from titles.cxb.schema import CxbProfileData, CxbScoreData, CxbItemData, CxbStaticData
|
||||||
|
|
||||||
|
|
||||||
class CxbData(Data):
|
class CxbData(Data):
|
||||||
def __init__(self, cfg: CoreConfig) -> None:
|
def __init__(self, cfg: CoreConfig) -> None:
|
||||||
super().__init__(cfg)
|
super().__init__(cfg)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user