diff --git a/core/title.py b/core/title.py index 6d83f46..8387065 100644 --- a/core/title.py +++ b/core/title.py @@ -1,4 +1,4 @@ -from twisted.web import resource +from typing import Dict, Any import logging, coloredlogs from logging.handlers import TimedRotatingFileHandler from twisted.web.http import Request @@ -13,6 +13,7 @@ class TitleServlet(): self.config = core_cfg self.config_folder = cfg_folder self.data = Data(core_cfg) + self.title_registry: Dict[str, Any] = {} self.logger = logging.getLogger("title") if not hasattr(self.logger, "initialized"): @@ -34,9 +35,32 @@ class TitleServlet(): if "game_registry" not in globals(): globals()["game_registry"] = Utils.get_all_titles() + + for folder, mod in globals()["game_registry"].items(): + if hasattr(mod, "game_codes") and hasattr(mod, "index"): + handler_cls = mod.index(self.config, self.config_folder) + if hasattr(handler_cls, "setup"): + handler_cls.setup() + + for code in mod.game_codes: + self.title_registry[code] = handler_cls + + else: + self.logger.error(f"{folder} missing game_code or index in __init__.py") + + self.logger.info(f"Serving {len(globals()['game_registry'])} game codes") + + def render_GET(self, request: Request, endpoints: dict) -> bytes: + print(endpoints) - def handle_GET(self, request: Request): - pass - - def handle_POST(self, request: Request): - pass \ No newline at end of file + def render_POST(self, request: Request, endpoints: dict) -> bytes: + print(endpoints) + code = endpoints["game"] + if code not in self.title_registry: + self.logger.warn(f"Unknown game code {code}") + + index = self.title_registry[code] + if not hasattr(index, "render_POST"): + self.logger.warn(f"{code} does not dispatch on POST") + + return index.render_POST(request, endpoints["version"], endpoints["endpoint"]) diff --git a/core/utils.py b/core/utils.py index bdb856d..a14f9ff 100644 --- a/core/utils.py +++ b/core/utils.py @@ -18,4 +18,5 @@ class Utils: except ImportError as e: print(f"{dir} - {e}") + raise return ret diff --git a/dbutils.py b/dbutils.py index b70b351..8e6024b 100644 --- a/dbutils.py +++ b/dbutils.py @@ -1,6 +1,47 @@ +import yaml import argparse +from core.config import CoreConfig +from core.data import Data -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="ARTEMiS main entry point") - parser.add_argument("--config", "-c", type=str, default="config", help="Configuration folder") - args = parser.parse_args() \ No newline at end of file +if __name__=='__main__': + parser = argparse.ArgumentParser(description="Database utilities") + parser.add_argument("--config", "-c", type=str, help="Config folder to use", default="config") + parser.add_argument("--version", "-v", type=str, help="Version of the database to upgrade/rollback to") + parser.add_argument("--game", "-g", type=str, help="Game code of the game who's schema will be updated/rolled back. Ex. SDFE") + parser.add_argument("action", type=str, help="DB Action, create, recreate, upgrade, or rollback") + args = parser.parse_args() + + cfg = CoreConfig() + cfg.update(yaml.safe_load(open(f"{args.config}/core.yaml"))) + data = Data(cfg) + + if args.action == "create": + data.create_database() + + elif args.action == "recreate": + data.recreate_database() + + elif args.action == "upgrade" or args.action == "rollback": + if args.version is None: + print("Must set game and version to migrate to") + exit(0) + + if args.game is None: + print("No game set, upgrading core schema") + data.migrate_database("CORE", int(args.version)) + + else: + data.migrate_database(args.game, int(args.version), args.action) + + elif args.action == "migrate": + print("Migrating from old schema to new schema") + data.restore_from_old_schema() + + elif args.action == "dump": + print("Dumping old schema to migrate to new schema") + data.dump_db() + + elif args.action == "generate": + pass + + data.logger.info("Done") diff --git a/example_config/chuni.yaml b/example_config/chuni.yaml new file mode 100644 index 0000000..3794f06 --- /dev/null +++ b/example_config/chuni.yaml @@ -0,0 +1,6 @@ +server: + enable: True + loglevel: "info" + +crypto: + encrypted_only: False \ No newline at end of file diff --git a/example_config/cxb.yaml b/example_config/cxb.yaml new file mode 100644 index 0000000..08948bd --- /dev/null +++ b/example_config/cxb.yaml @@ -0,0 +1,9 @@ +server: + enable: True + loglevel: "info" + hostname: "localhost" + ssl_enable: False + port: 8082 + port_secure: 443 + ssl_cert: "cert/title.crt" + ssl_key: "cert/title.key" diff --git a/example_config/diva.yaml b/example_config/diva.yaml new file mode 100644 index 0000000..1354910 --- /dev/null +++ b/example_config/diva.yaml @@ -0,0 +1,4 @@ +server: + enable: True + loglevel: "info" + diff --git a/example_config/mai2.yaml b/example_config/mai2.yaml new file mode 100644 index 0000000..a04dda5 --- /dev/null +++ b/example_config/mai2.yaml @@ -0,0 +1,3 @@ +server: + enable: True + loglevel: "info" diff --git a/example_config/nginx_example.conf b/example_config/nginx_example.conf new file mode 100644 index 0000000..3b8bf3c --- /dev/null +++ b/example_config/nginx_example.conf @@ -0,0 +1,60 @@ +# Allnet +server { + listen 80; + server_name naominet.jp; + + location / { + proxy_pass http://localhost:8000/; + } +} + +# Non-SSL titles +server { + listen 80; + server_name your.hostname.here; + + location / { + proxy_pass http://localhost:8080/; + } +} + +# SSL titles +server { + listen 443 ssl default_server; + listen [::]:443 ssl default_server; + server_name your.hostname.here; + + ssl_certificate /path/to/cert/title.crt; + ssl_certificate_key /path/to/cert/title.key; + ssl_session_timeout 1d; + ssl_session_cache shared:MozSSL:10m; + ssl_session_tickets off; + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; + ssl_ciphers "ALL:@SECLEVEL=0"; + ssl_prefer_server_ciphers off; + + location / { + proxy_pass http://localhost:8080/; + } +} + +# Billing +server { + listen 8443 ssl; + server_name ib.naominet.jp; + + ssl_certificate /path/to/cert/server.pem; + ssl_certificate_key /path/to/cert/server.key; + ssl_session_timeout 1d; + ssl_session_cache shared:MozSSL:10m; + ssl_session_tickets off; + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; + ssl_ciphers "ALL:@SECLEVEL=1"; + ssl_prefer_server_ciphers off; + + location / { + proxy_pass http://localhost:8444/; + } +} \ No newline at end of file diff --git a/example_config/ongeki.yaml b/example_config/ongeki.yaml new file mode 100644 index 0000000..a04dda5 --- /dev/null +++ b/example_config/ongeki.yaml @@ -0,0 +1,3 @@ +server: + enable: True + loglevel: "info" diff --git a/example_config/wacca.yaml b/example_config/wacca.yaml new file mode 100644 index 0000000..4c898a4 --- /dev/null +++ b/example_config/wacca.yaml @@ -0,0 +1,32 @@ +server: + enable: True + loglevel: "info" + +mods: + always_vip: True + infinite_tickets: True + infinite_wp: True + +gates: + enabled_gates: + - 1 + - 2 + - 3 + - 4 + - 5 + - 6 + - 7 + - 8 + - 9 + - 10 + - 11 + - 12 + - 13 + - 14 + - 15 + - 16 + - 17 + - 18 + - 19 + - 21 + - 22 diff --git a/index.py b/index.py index ad712b5..8b8ee86 100644 --- a/index.py +++ b/index.py @@ -4,10 +4,83 @@ import yaml from os import path, mkdir, access, W_OK from core import * -from twisted.web import server +from twisted.web import server, resource from twisted.internet import reactor, endpoints -from txroutes import Dispatcher +from twisted.web.http import Request +from routes import Mapper +class HttpDispatcher(resource.Resource): + def __init__(self, cfg: CoreConfig, config_dir: str): + super().__init__() + self.config = cfg + self.isLeaf = True + self.map_get = Mapper() + self.map_post = Mapper() + + self.allnet = AllnetServlet(cfg, config_dir) + self.title = TitleServlet(cfg, config_dir) + + 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_get.connect("title_get", "/{game}/{version}/{endpoint:.*?}", controller="title", action="render_GET", requirements=dict(game=R"S...")) + self.map_post.connect("title_post", "/{game}/{version}/{endpoint:.*?}", controller="title", action="render_POST", requirements=dict(game=R"S...")) + + def render_POST(self, request: Request) -> bytes: + test = self.map_get.match(request.uri.decode()) + if test is None: + return b"" + + controller = getattr(self, test["controller"], None) + if controller is None: + return b"" + + handler = getattr(controller, test["action"], None) + if handler is None: + return b"" + + url_vars = test + url_vars.pop("controller") + url_vars.pop("action") + + if len(url_vars) > 0: + ret = handler(request, url_vars) + else: + ret = handler(request) + + if type(ret) == str: + return ret.encode() + elif type(ret) == bytes: + return ret + else: + return b"" + + def render_POST(self, request: Request) -> bytes: + test = self.map_post.match(request.uri.decode()) + if test is None: + return b"" + + controller = getattr(self, test["controller"], None) + if controller is None: + return b"" + + handler = getattr(controller, test["action"], None) + if handler is None: + return b"" + + url_vars = test + url_vars.pop("controller") + url_vars.pop("action") + ret = handler(request, url_vars) + + if type(ret) == str: + return ret.encode() + elif type(ret) == bytes: + return ret + else: + return b"" + if __name__ == "__main__": parser = argparse.ArgumentParser(description="ARTEMiS main entry point") parser.add_argument("--config", "-c", type=str, default="config", help="Configuration folder") @@ -42,16 +115,8 @@ if __name__ == "__main__": billing_server_str = f"ssl:{cfg.billing.port}:interface={cfg.server.listen_address}"\ f":privateKey={cfg.billing.ssl_key}:certKey={cfg.billing.ssl_cert}" - allnet_cls = AllnetServlet(cfg, args.config) - title_cls = TitleServlet(cfg, args.config) + dispatcher = HttpDispatcher(cfg, args.config) - dispatcher = Dispatcher() - dispatcher.connect('allnet_poweron', '/sys/servlet/PowerOn', allnet_cls, action='handle_poweron', conditions=dict(method=['POST'])) - dispatcher.connect('allnet_downloadorder', '/sys/servlet/DownloadOrder', allnet_cls, action='handle_dlorder', conditions=dict(method=['POST'])) - dispatcher.connect('allnet_billing', '/request', allnet_cls, action='handle_billing_request', conditions=dict(method=['POST'])) - dispatcher.connect("title_get", "/{game}/{version}/{endpoint}", title_cls, action="handle_GET", conditions=dict(method=['GET'])) - dispatcher.connect("title_post", "/{game}/{version}/{endpoint}", title_cls, action="handle_POST", conditions=dict(method=['POST'])) - endpoints.serverFromString(reactor, allnet_server_str).listen(server.Site(dispatcher)) endpoints.serverFromString(reactor, adb_server_str).listen(AimedbFactory(cfg)) diff --git a/read.py b/read.py new file mode 100644 index 0000000..ffc4812 --- /dev/null +++ b/read.py @@ -0,0 +1,129 @@ +# vim: set fileencoding=utf-8 +import argparse +import re +import os +import yaml +import importlib +import logging, coloredlogs + +from logging.handlers import TimedRotatingFileHandler +from typing import List, Optional + +from core import CoreConfig +from core.utils import Utils + +class BaseReader(): + def __init__(self, config: CoreConfig, version: int, bin_dir: Optional[str], opt_dir: Optional[str], extra: Optional[str]) -> None: + self.logger = logging.getLogger("reader") + self.config = config + self.bin_dir = bin_dir + self.opt_dir = opt_dir + self.version = version + self.extra = extra + + + def get_data_directories(self, directory: str) -> List[str]: + ret: List[str] = [] + + for root, dirs, files in os.walk(directory): + for dir in dirs: + if re.fullmatch("[A-Z0-9]{4,4}", dir) is not None: + ret.append(f"{root}/{dir}") + + return ret + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Import Game Information') + parser.add_argument( + '--series', + action='store', + type=str, + required=True, + help='The game series we are importing.', + ) + parser.add_argument( + '--version', + dest='version', + action='store', + type=int, + required=True, + help='The game version we are importing.', + ) + parser.add_argument( + '--binfolder', + dest='bin', + action='store', + type=str, + help='Folder containing A000 base data', + ) + parser.add_argument( + '--optfolder', + dest='opt', + action='store', + type=str, + help='Folder containing Option data folders', + ) + parser.add_argument( + "--config", + type=str, + default="config", + help="Folder containing the core configuration for importing to DB. Defaults to 'config'.", + ) + parser.add_argument( + "--extra", + type=str, + help="Any extra data that a reader might require.", + ) + + # Parse args, validate invariants. + args = parser.parse_args() + + config = CoreConfig() + config.update(yaml.safe_load(open(f"{args.config}/core.yaml"))) + + log_fmt_str = "[%(asctime)s] Reader | %(levelname)s | %(message)s" + log_fmt = logging.Formatter(log_fmt_str) + logger = logging.getLogger("reader") + + fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(config.server.logs, "reader"), when="d", backupCount=10) + fileHandler.setFormatter(log_fmt) + + consoleHandler = logging.StreamHandler() + consoleHandler.setFormatter(log_fmt) + + logger.addHandler(fileHandler) + logger.addHandler(consoleHandler) + + logger.setLevel(logging.INFO) + coloredlogs.install(level=logging.INFO, logger=logger, fmt=log_fmt_str) + + if args.series is None or args.version is None: + logger.error("Game or version not specified") + parser.print_help() + exit(1) + + if args.bin is None and args.opt is None: + logger.error("Must specify either bin or opt directory") + parser.print_help() + exit(1) + + if args.bin is not None and (args.bin.endswith("\\") or args.bin.endswith("/")): + bin_arg = args.bin[:-1] + else: + bin_arg = args.bin + + if args.opt is not None and (args.opt.endswith("\\") or args.opt.endswith("/")): + opt_arg = args.opt[:-1] + else: + opt_arg = args.opt + + logger.info("Starting importer...") + + titles = Utils.get_all_titles() + + for dir, mod in titles.items(): + if args.series in mod.game_codes: + handler = mod.reader(config, args.version, bin_arg, opt_arg, args.extra) + handler.read() + + logger.info("Done") diff --git a/requirements.txt b/requirements.txt index 8dffde0..9ab1628 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,4 +12,4 @@ inflection coloredlogs pylibmc wacky -txroutes +Routes diff --git a/requirements_win.txt b/requirements_win.txt index 412f961..f5804d3 100644 --- a/requirements_win.txt +++ b/requirements_win.txt @@ -11,4 +11,4 @@ PyCryptodome inflection coloredlogs wacky -txroutes +Routes diff --git a/titles/chuni/__init__.py b/titles/chuni/__init__.py new file mode 100644 index 0000000..3883aeb --- /dev/null +++ b/titles/chuni/__init__.py @@ -0,0 +1,18 @@ +from titles.chuni.index import ChuniServlet +from titles.chuni.const import ChuniConstants +from titles.chuni.database import ChuniData +from titles.chuni.read import ChuniReader + +index = ChuniServlet +database = ChuniData +reader = ChuniReader + +use_default_title = True +include_protocol = True +title_secure = False +game_codes = [ChuniConstants.GAME_CODE, ChuniConstants.GAME_CODE_NEW] +trailing_slash = True +use_default_host = False +host = "" + +current_schema_version = 1 diff --git a/titles/chuni/air.py b/titles/chuni/air.py new file mode 100644 index 0000000..46f8337 --- /dev/null +++ b/titles/chuni/air.py @@ -0,0 +1,16 @@ +from typing import Dict + +from core.config import CoreConfig +from titles.chuni.base import ChuniBase +from titles.chuni.const import ChuniConstants +from titles.chuni.config import ChuniConfig + +class ChuniAir(ChuniBase): + def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None: + super().__init__(core_cfg, game_cfg) + self.version = ChuniConstants.VER_CHUNITHM_AIR + + def handle_get_game_setting_api_request(self, data: Dict) -> Dict: + ret = super().handle_get_game_setting_api_request(data) + ret["gameSetting"]["dataVersion"] = "1.10.00" + return ret \ No newline at end of file diff --git a/titles/chuni/airplus.py b/titles/chuni/airplus.py new file mode 100644 index 0000000..77498fb --- /dev/null +++ b/titles/chuni/airplus.py @@ -0,0 +1,16 @@ +from typing import Dict + +from core.config import CoreConfig +from titles.chuni.base import ChuniBase +from titles.chuni.const import ChuniConstants +from titles.chuni.config import ChuniConfig + +class ChuniAirPlus(ChuniBase): + def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None: + super().__init__(core_cfg, game_cfg) + self.version = ChuniConstants.VER_CHUNITHM_AIR_PLUS + + def handle_get_game_setting_api_request(self, data: Dict) -> Dict: + ret = super().handle_get_game_setting_api_request(data) + ret["gameSetting"]["dataVersion"] = "1.15.00" + return ret \ No newline at end of file diff --git a/titles/chuni/amazon.py b/titles/chuni/amazon.py new file mode 100644 index 0000000..d822665 --- /dev/null +++ b/titles/chuni/amazon.py @@ -0,0 +1,18 @@ +from datetime import datetime, timedelta +from typing import Dict, Any +import pytz + +from core.config import CoreConfig +from titles.chuni.base import ChuniBase +from titles.chuni.const import ChuniConstants +from titles.chuni.config import ChuniConfig + +class ChuniAmazon(ChuniBase): + def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None: + super().__init__(core_cfg, game_cfg) + self.version = ChuniConstants.VER_CHUNITHM_AMAZON + + def handle_get_game_setting_api_request(self, data: Dict) -> Dict: + ret = super().handle_get_game_setting_api_request(data) + ret["gameSetting"]["dataVersion"] = "1.30.00" + return ret diff --git a/titles/chuni/amazonplus.py b/titles/chuni/amazonplus.py new file mode 100644 index 0000000..5e901cd --- /dev/null +++ b/titles/chuni/amazonplus.py @@ -0,0 +1,18 @@ +from datetime import datetime, timedelta +from typing import Dict, Any +import pytz + +from core.config import CoreConfig +from titles.chuni.base import ChuniBase +from titles.chuni.const import ChuniConstants +from titles.chuni.config import ChuniConfig + +class ChuniAmazonPlus(ChuniBase): + def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None: + super().__init__(core_cfg, game_cfg) + self.version = ChuniConstants.VER_CHUNITHM_AMAZON_PLUS + + def handle_get_game_setting_api_request(self, data: Dict) -> Dict: + ret = super().handle_get_game_setting_api_request(data) + ret["gameSetting"]["dataVersion"] = "1.35.00" + return ret diff --git a/titles/chuni/base.py b/titles/chuni/base.py new file mode 100644 index 0000000..bf32f63 --- /dev/null +++ b/titles/chuni/base.py @@ -0,0 +1,572 @@ +import logging +import json +from datetime import datetime, timedelta +from time import strftime + +import pytz +from typing import Dict, Any + +from core.config import CoreConfig +from titles.chuni.const import ChuniConstants +from titles.chuni.database import ChuniData +from titles.chuni.config import ChuniConfig + +class ChuniBase(): + def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None: + self.core_cfg = core_cfg + self.game_cfg = game_cfg + self.data = ChuniData(core_cfg) + self.date_time_format = "%Y-%m-%d %H:%M:%S" + self.logger = logging.getLogger("chuni") + self.game = ChuniConstants.GAME_CODE + self.version = ChuniConstants.VER_CHUNITHM + + def handle_game_login_api_request(self, data: Dict) -> Dict: + #self.data.base.log_event("chuni", "login", logging.INFO, {"version": self.version, "user": data["userId"]}) + return { "returnCode": 1 } + + def handle_game_logout_api_request(self, data: Dict) -> Dict: + #self.data.base.log_event("chuni", "logout", logging.INFO, {"version": self.version, "user": data["userId"]}) + return { "returnCode": 1 } + + def handle_get_game_charge_api_request(self, data: Dict) -> Dict: + game_charge_list = self.data.static.get_enabled_charges(self.version) + + charges = [] + for x in range(len(game_charge_list)): + charges.append({ + "orderId": x, + "chargeId": game_charge_list[x]["chargeId"], + "price": 1, + "startDate": "2017-12-05 07:00:00.0", + "endDate": "2099-12-31 00:00:00.0", + "salePrice": 1, + "saleStartDate": "2017-12-05 07:00:00.0", + "saleEndDate": "2099-12-31 00:00:00.0" + }) + return { + "length": len(charges), + "gameChargeList": charges + } + + def handle_get_game_event_api_request(self, data: Dict) -> Dict: + game_events = self.data.static.get_enabled_events(self.version) + + event_list = [] + for evt_row in game_events: + tmp = {} + tmp["id"] = evt_row["eventId"] + tmp["type"] = evt_row["type"] + tmp["startDate"] = "2017-12-05 07:00:00.0" + tmp["endDate"] = "2099-12-31 00:00:00.0" + event_list.append(tmp) + + return { + "type": data["type"], + "length": len(event_list), + "gameEventList": event_list + } + + def handle_get_game_idlist_api_request(self, data: Dict) -> Dict: + return { "type": data["type"], "length": 0, "gameIdlistList": [] } + + def handle_get_game_message_api_request(self, data: Dict) -> Dict: + return { "type": data["type"], "length": "0", "gameMessageList": [] } + + def handle_get_game_ranking_api_request(self, data: Dict) -> Dict: + return { "type": data["type"], "gameRankingList": [] } + + def handle_get_game_sale_api_request(self, data: Dict) -> Dict: + return { "type": data["type"], "length": 0, "gameSaleList": [] } + + def handle_get_game_setting_api_request(self, data: Dict) -> Dict: + reboot_start = datetime.strftime(datetime.now() - timedelta(hours=4), self.date_time_format) + reboot_end = datetime.strftime(datetime.now() - timedelta(hours=3), self.date_time_format) + return { + "gameSetting": { + "dataVersion": "1.00.00", + "isMaintenance": "false", + "requestInterval": 10, + "rebootStartTime": reboot_start, + "rebootEndTime": reboot_end, + "isBackgroundDistribute": "false", + "maxCountCharacter": 300, + "maxCountItem": 300, + "maxCountMusic": 300, + }, + "isDumpUpload": "false", + "isAou": "false", + } + + def handle_get_user_activity_api_request(self, data: Dict) -> Dict: + user_activity_list = self.data.profile.get_profile_activity(data["userId"], data["kind"]) + + activity_list = [] + + for activity in user_activity_list: + tmp = activity._asdict() + tmp.pop("user") + tmp["id"] = tmp["activityId"] + tmp.pop("activityId") + activity_list.append(tmp) + + return { + "userId": data["userId"], + "length": len(activity_list), + "kind": data["kind"], + "userActivityList": activity_list + } + + def handle_get_user_character_api_request(self, data: Dict) -> Dict: + characters = self.data.item.get_characters(data["userId"]) + if characters is None: return {} + next_idx = -1 + + characterList = [] + for x in range(int(data["nextIndex"]), len(characters)): + tmp = characters[x]._asdict() + tmp.pop("user") + tmp.pop("id") + characterList.append(tmp) + + if len(characterList) >= int(data["maxCount"]): + break + + if len(characterList) >= int(data["maxCount"]) and len(characters) > int(data["maxCount"]) + int(data["nextIndex"]): + next_idx = int(data["maxCount"]) + int(data["nextIndex"]) + 1 + + return { + "userId": data["userId"], + "length": len(characterList), + "nextIndex": next_idx, + "userCharacterList": characterList + } + + def handle_get_user_charge_api_request(self, data: Dict) -> Dict: + user_charge_list = self.data.profile.get_profile_charge(data["userId"]) + + charge_list = [] + for charge in user_charge_list: + tmp = charge._asdict() + tmp.pop("id") + tmp.pop("user") + charge_list.append(tmp) + + return { + "userId": data["userId"], + "length": len(charge_list), + "userChargeList": charge_list + } + + def handle_get_user_course_api_request(self, data: Dict) -> Dict: + user_course_list = self.data.score.get_courses(data["userId"]) + if user_course_list is None: + return { + "userId": data["userId"], + "length": 0, + "nextIndex": -1, + "userCourseList": [] + } + + course_list = [] + next_idx = int(data["nextIndex"]) + max_ct = int(data["maxCount"]) + + for x in range(next_idx, len(user_course_list)): + tmp = user_course_list[x]._asdict() + tmp.pop("user") + tmp.pop("id") + course_list.append(tmp) + + if len(user_course_list) >= max_ct: + break + + if len(user_course_list) >= max_ct: + next_idx = next_idx + max_ct + else: + next_idx = -1 + + return { + "userId": data["userId"], + "length": len(course_list), + "nextIndex": next_idx, + "userCourseList": course_list + } + + def handle_get_user_data_api_request(self, data: Dict) -> Dict: + p = self.data.profile.get_profile_data(data["userId"], self.version) + if p is None: return {} + + profile = p._asdict() + profile.pop("id") + profile.pop("user") + profile.pop("version") + + return { + "userId": data["userId"], + "userData": profile + } + + def handle_get_user_data_ex_api_request(self, data: Dict) -> Dict: + p = self.data.profile.get_profile_data_ex(data["userId"], self.version) + if p is None: return {} + + profile = p._asdict() + profile.pop("id") + profile.pop("user") + profile.pop("version") + + return { + "userId": data["userId"], + "userDataEx": profile + } + + def handle_get_user_duel_api_request(self, data: Dict) -> Dict: + user_duel_list = self.data.item.get_duels(data["userId"]) + if user_duel_list is None: return {} + + duel_list = [] + for duel in user_duel_list: + tmp = duel._asdict() + tmp.pop("id") + tmp.pop("user") + duel_list.append(tmp) + + return { + "userId": data["userId"], + "length": len(duel_list), + "userDuelList": duel_list + } + + def handle_get_user_favorite_item_api_request(self, data: Dict) -> Dict: + return { + "userId": data["userId"], + "length": 0, + "kind": data["kind"], + "nextIndex": -1, + "userFavoriteItemList": [] + } + + def handle_get_user_favorite_music_api_request(self, data: Dict) -> Dict: + """ + This is handled via the webui, which we don't have right now + """ + + return { + "userId": data["userId"], + "length": 0, + "userFavoriteMusicList": [] + } + + def handle_get_user_item_api_request(self, data: Dict) -> Dict: + kind = int(int(data["nextIndex"]) / 10000000000) + next_idx = int(int(data["nextIndex"]) % 10000000000) + user_item_list = self.data.item.get_items(data["userId"], kind) + + if user_item_list is None or len(user_item_list) == 0: + return {"userId": data["userId"], "nextIndex": -1, "itemKind": kind, "userItemList": []} + + items: list[Dict[str, Any]] = [] + for i in range(next_idx, len(user_item_list)): + tmp = user_item_list[i]._asdict() + tmp.pop("user") + tmp.pop("id") + items.append(tmp) + if len(items) >= int(data["maxCount"]): + break + + xout = kind * 10000000000 + next_idx + len(items) + + if len(items) < int(data["maxCount"]): nextIndex = 0 + else: nextIndex = xout + + return {"userId": data["userId"], "nextIndex": nextIndex, "itemKind": kind, "length": len(items), "userItemList": items} + + def handle_get_user_login_bonus_api_request(self, data: Dict) -> Dict: + """ + Unsure how to get this to trigger... + """ + return { + "userId": data["userId"], + "length": 2, + "userLoginBonusList": [ + { + "presetId": '10', + "bonusCount": '0', + "lastUpdateDate": "1970-01-01 09:00:00", + "isWatched": "true" + }, + { + "presetId": '20', + "bonusCount": '0', + "lastUpdateDate": "1970-01-01 09:00:00", + "isWatched": "true" + }, + ] + } + + def handle_get_user_map_api_request(self, data: Dict) -> Dict: + user_map_list = self.data.item.get_maps(data["userId"]) + if user_map_list is None: return {} + + map_list = [] + for map in user_map_list: + tmp = map._asdict() + tmp.pop("id") + tmp.pop("user") + map_list.append(tmp) + + return { + "userId": data["userId"], + "length": len(map_list), + "userMapList": map_list + } + + def handle_get_user_music_api_request(self, data: Dict) -> Dict: + music_detail = self.data.score.get_scores(data["userId"]) + if music_detail is None: + return { + "userId": data["userId"], + "length": 0, + "nextIndex": -1, + "userMusicList": [] #240 + } + song_list = [] + next_idx = int(data["nextIndex"]) + max_ct = int(data["maxCount"]) + + for x in range(next_idx, len(music_detail)): + found = False + tmp = music_detail[x]._asdict() + tmp.pop("user") + tmp.pop("id") + + for song in song_list: + if song["userMusicDetailList"][0]["musicId"] == tmp["musicId"]: + found = True + song["userMusicDetailList"].append(tmp) + song["length"] = len(song["userMusicDetailList"]) + + if not found: + song_list.append({ + "length": 1, + "userMusicDetailList": [tmp] + }) + + if len(song_list) >= max_ct: + break + + if len(song_list) >= max_ct: + next_idx += max_ct + else: + next_idx = 0 + + return { + "userId": data["userId"], + "length": len(song_list), + "nextIndex": next_idx, + "userMusicList": song_list #240 + } + + def handle_get_user_option_api_request(self, data: Dict) -> Dict: + p = self.data.profile.get_profile_option(data["userId"]) + + option = p._asdict() + option.pop("id") + option.pop("user") + + return { + "userId": data["userId"], + "userGameOption": option + } + + def handle_get_user_option_ex_api_request(self, data: Dict) -> Dict: + p = self.data.profile.get_profile_option_ex(data["userId"]) + + option = p._asdict() + option.pop("id") + option.pop("user") + + return { + "userId": data["userId"], + "userGameOptionEx": option + } + + def read_wtf8(self, src): + return bytes([ord(c) for c in src]).decode("utf-8") + + def handle_get_user_preview_api_request(self, data: Dict) -> Dict: + profile = self.data.profile.get_profile_preview(data["userId"], self.version) + if profile is None: return None + profile_character = self.data.item.get_character(data["userId"], profile["characterId"]) + + if profile_character is None: + chara = {} + else: + chara = profile_character._asdict() + chara.pop("id") + chara.pop("user") + + return { + "userId": data["userId"], + # Current Login State + "isLogin": False, + "lastLoginDate": profile["lastPlayDate"], + # User Profile + "userName": profile["userName"], + "reincarnationNum": profile["reincarnationNum"], + "level": profile["level"], + "exp": profile["exp"], + "playerRating": profile["playerRating"], + "lastGameId": profile["lastGameId"], + "lastRomVersion": profile["lastRomVersion"], + "lastDataVersion": profile["lastDataVersion"], + "lastPlayDate": profile["lastPlayDate"], + "trophyId": profile["trophyId"], + "nameplateId": profile["nameplateId"], + # Current Selected Character + "userCharacter": chara, + # User Game Options + "playerLevel": profile["playerLevel"], + "rating": profile["rating"], + "headphone": profile["headphone"], + "chargeState": "1", + "userNameEx": profile["userName"], + } + + def handle_get_user_recent_rating_api_request(self, data: Dict) -> Dict: + recet_rating_list = self.data.profile.get_profile_recent_rating(data["userId"]) + if recet_rating_list is None: + return { + "userId": data["userId"], + "length": 0, + "userRecentRatingList": [], + } + + return { + "userId": data["userId"], + "length": len(recet_rating_list["recentRating"]), + "userRecentRatingList": recet_rating_list["recentRating"], + } + + def handle_get_user_region_api_request(self, data: Dict) -> Dict: + # TODO: Region + return { + "userId": data["userId"], + "length": 0, + "userRegionList": [], + } + + def handle_get_user_team_api_request(self, data: Dict) -> Dict: + # TODO: Team + return { + "userId": data["userId"], + "teamId": 0 + } + + def handle_get_team_course_setting_api_request(self, data: Dict) -> Dict: + return { + "userId": data["userId"], + "length": 0, + "nextIndex": 0, + "teamCourseSettingList": [], + } + + def handle_get_team_course_rule_api_request(self, data: Dict) -> Dict: + return { + "userId": data["userId"], + "length": 0, + "nextIndex": 0, + "teamCourseRuleList": [], + } + + def handle_upsert_user_all_api_request(self, data: Dict) -> Dict: + upsert = data["upsertUserAll"] + user_id = data["userId"] + + if "userData" in upsert: + try: + upsert["userData"][0]["userName"] = self.read_wtf8(upsert["userData"][0]["userName"]) + except: pass + + self.data.profile.put_profile_data(user_id, self.version, upsert["userData"][0]) + if "userDataEx" in upsert: + self.data.profile.put_profile_data_ex(user_id, self.version, upsert["userDataEx"][0]) + if "userGameOption" in upsert: + self.data.profile.put_profile_option(user_id, upsert["userGameOption"][0]) + if "userGameOptionEx" in upsert: + self.data.profile.put_profile_option_ex(user_id, upsert["userGameOptionEx"][0]) + if "userRecentRatingList" in upsert: + self.data.profile.put_profile_recent_rating(user_id, upsert["userRecentRatingList"]) + + if "userCharacterList" in upsert: + for character in upsert["userCharacterList"]: + self.data.item.put_character(user_id, character) + + if "userMapList" in upsert: + for map in upsert["userMapList"]: + self.data.item.put_map(user_id, map) + + if "userCourseList" in upsert: + for course in upsert["userCourseList"]: + self.data.score.put_course(user_id, course) + + if "userDuelList" in upsert: + for duel in upsert["userDuelList"]: + self.data.item.put_duel(user_id, duel) + + if "userItemList" in upsert: + for item in upsert["userItemList"]: + self.data.item.put_item(user_id, item) + + if "userActivityList" in upsert: + for activity in upsert["userActivityList"]: + self.data.profile.put_profile_activity(user_id, activity) + + if "userChargeList" in upsert: + for charge in upsert["userChargeList"]: + self.data.profile.put_profile_charge(user_id, charge) + + if "userMusicDetailList" in upsert: + for song in upsert["userMusicDetailList"]: + self.data.score.put_score(user_id, song) + + if "userPlaylogList" in upsert: + for playlog in upsert["userPlaylogList"]: + self.data.score.put_playlog(user_id, playlog) + + if "userTeamPoint" in upsert: + # TODO: team stuff + pass + + if "userMapAreaList" in upsert: + for map_area in upsert["userMapAreaList"]: + self.data.item.put_map_area(user_id, map_area) + + if "userOverPowerList" in upsert: + for overpower in upsert["userOverPowerList"]: + self.data.profile.put_profile_overpower(user_id, overpower) + + if "userEmoneyList" in upsert: + for emoney in upsert["userEmoneyList"]: + self.data.profile.put_profile_emoney(user_id, emoney) + + return { "returnCode": "1" } + + def handle_upsert_user_chargelog_api_request(self, data: Dict) -> Dict: + return { "returnCode": "1" } + + def handle_upsert_client_bookkeeping_api_request(self, data: Dict) -> Dict: + return { "returnCode": "1" } + + def handle_upsert_client_develop_api_request(self, data: Dict) -> Dict: + return { "returnCode": "1" } + + def handle_upsert_client_error_api_request(self, data: Dict) -> Dict: + return { "returnCode": "1" } + + def handle_upsert_client_setting_api_request(self, data: Dict) -> Dict: + return { "returnCode": "1" } + + def handle_upsert_client_testmode_api_request(self, data: Dict) -> Dict: + return { "returnCode": "1" } diff --git a/titles/chuni/config.py b/titles/chuni/config.py new file mode 100644 index 0000000..f3a0a7e --- /dev/null +++ b/titles/chuni/config.py @@ -0,0 +1,36 @@ +from core.config import CoreConfig +from typing import Dict + +class ChuniServerConfig(): + def __init__(self, parent_config: "ChuniConfig") -> None: + self.__config = parent_config + + @property + def enable(self) -> bool: + return CoreConfig.get_config_field(self.__config, 'chuni', 'server', 'enable', default=True) + + @property + def loglevel(self) -> int: + return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'chuni', 'server', 'loglevel', default="info")) + +class ChuniCryptoConfig(): + def __init__(self, parent_config: "ChuniConfig") -> None: + self.__config = parent_config + + @property + def keys(self) -> Dict: + """ + in the form of: + internal_version: [key, iv] + all values are hex strings + """ + return CoreConfig.get_config_field(self.__config, 'chuni', 'crypto', 'keys', default={}) + + @property + def encrypted_only(self) -> bool: + return CoreConfig.get_config_field(self.__config, 'chuni', 'crypto', 'encrypted_only', default=False) + +class ChuniConfig(dict): + def __init__(self) -> None: + self.server = ChuniServerConfig(self) + self.crypto = ChuniCryptoConfig(self) \ No newline at end of file diff --git a/titles/chuni/const.py b/titles/chuni/const.py new file mode 100644 index 0000000..ebc8cf2 --- /dev/null +++ b/titles/chuni/const.py @@ -0,0 +1,24 @@ +class ChuniConstants(): + GAME_CODE = "SDBT" + GAME_CODE_NEW = "SDHD" + + VER_CHUNITHM = 0 + VER_CHUNITHM_PLUS = 1 + VER_CHUNITHM_AIR = 2 + VER_CHUNITHM_AIR_PLUS = 3 + VER_CHUNITHM_STAR = 4 + VER_CHUNITHM_STAR_PLUS = 5 + VER_CHUNITHM_AMAZON = 6 + VER_CHUNITHM_AMAZON_PLUS = 7 + VER_CHUNITHM_CRYSTAL = 8 + VER_CHUNITHM_CRYSTAL_PLUS = 9 + VER_CHUNITHM_PARADISE = 10 + VER_CHUNITHM_NEW = 11 + VER_CHUNITHM_NEW_PLUS = 12 + + VERSION_NAMES = ["Chunithm", "Chunithm+", "Chunithm Air", "Chunithm Air+", "Chunithm Star", "Chunithm Star+", "Chunithm Amazon", + "Chunithm Amazon+", "Chunithm Crystal", "Chunithm Crystal+", "Chunithm Paradise", "Chunithm New!!", "Chunithm New!!+"] + + @classmethod + def game_ver_to_string(cls, ver: int): + return cls.VERSION_NAMES[ver] diff --git a/titles/chuni/crystal.py b/titles/chuni/crystal.py new file mode 100644 index 0000000..d492f0b --- /dev/null +++ b/titles/chuni/crystal.py @@ -0,0 +1,18 @@ +from datetime import datetime, timedelta +from typing import Dict, Any +import pytz + +from core.config import CoreConfig +from titles.chuni.base import ChuniBase +from titles.chuni.const import ChuniConstants +from titles.chuni.config import ChuniConfig + +class ChuniCrystal(ChuniBase): + def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None: + super().__init__(core_cfg, game_cfg) + self.version = ChuniConstants.VER_CHUNITHM_CRYSTAL + + def handle_get_game_setting_api_request(self, data: Dict) -> Dict: + ret = super().handle_get_game_setting_api_request(data) + ret["gameSetting"]["dataVersion"] = "1.40.00" + return ret diff --git a/titles/chuni/crystalplus.py b/titles/chuni/crystalplus.py new file mode 100644 index 0000000..b06eb5b --- /dev/null +++ b/titles/chuni/crystalplus.py @@ -0,0 +1,18 @@ +from datetime import datetime, timedelta +from typing import Dict, Any +import pytz + +from core.config import CoreConfig +from titles.chuni.base import ChuniBase +from titles.chuni.const import ChuniConstants +from titles.chuni.config import ChuniConfig + +class ChuniCrystalPlus(ChuniBase): + def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None: + super().__init__(core_cfg, game_cfg) + self.version = ChuniConstants.VER_CHUNITHM_CRYSTAL_PLUS + + def handle_get_game_setting_api_request(self, data: Dict) -> Dict: + ret = super().handle_get_game_setting_api_request(data) + ret["gameSetting"]["dataVersion"] = "1.45.00" + return ret diff --git a/titles/chuni/database.py b/titles/chuni/database.py new file mode 100644 index 0000000..c55149b --- /dev/null +++ b/titles/chuni/database.py @@ -0,0 +1,12 @@ +from core.data import Data +from core.config import CoreConfig +from titles.chuni.schema import * + +class ChuniData(Data): + def __init__(self, cfg: CoreConfig) -> None: + super().__init__(cfg) + + self.item = ChuniItemData(cfg, self.session) + self.profile = ChuniProfileData(cfg, self.session) + self.score = ChuniScoreData(cfg, self.session) + self.static = ChuniStaticData(cfg, self.session) \ No newline at end of file diff --git a/titles/chuni/index.py b/titles/chuni/index.py new file mode 100644 index 0000000..25f31b5 --- /dev/null +++ b/titles/chuni/index.py @@ -0,0 +1,172 @@ +from twisted.web.http import Request +import logging, coloredlogs +from logging.handlers import TimedRotatingFileHandler +import zlib +import yaml +import json +import inflection +import string +from Crypto.Cipher import AES +from Crypto.Util.Padding import pad + +from core import CoreConfig +from titles.chuni.config import ChuniConfig +from titles.chuni.const import ChuniConstants +from titles.chuni.base import ChuniBase +from titles.chuni.plus import ChuniPlus +from titles.chuni.air import ChuniAir +from titles.chuni.airplus import ChuniAirPlus +from titles.chuni.star import ChuniStar +from titles.chuni.starplus import ChuniStarPlus +from titles.chuni.amazon import ChuniAmazon +from titles.chuni.amazonplus import ChuniAmazonPlus +from titles.chuni.crystal import ChuniCrystal +from titles.chuni.crystalplus import ChuniCrystalPlus +from titles.chuni.paradise import ChuniParadise +from titles.chuni.new import ChuniNew +from titles.chuni.newplus import ChuniNewPlus + +class ChuniServlet(): + def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None: + self.core_cfg = core_cfg + self.game_cfg = ChuniConfig() + self.game_cfg.update(yaml.safe_load(open(f"{cfg_dir}/chuni.yaml"))) + + self.versions = [ + ChuniBase(core_cfg, self.game_cfg), + ChuniPlus(core_cfg, self.game_cfg), + ChuniAir(core_cfg, self.game_cfg), + ChuniAirPlus(core_cfg, self.game_cfg), + ChuniStar(core_cfg, self.game_cfg), + ChuniStarPlus(core_cfg, self.game_cfg), + ChuniAmazon(core_cfg, self.game_cfg), + ChuniAmazonPlus(core_cfg, self.game_cfg), + ChuniCrystal(core_cfg, self.game_cfg), + ChuniCrystalPlus(core_cfg, self.game_cfg), + ChuniParadise(core_cfg, self.game_cfg), + ChuniNew(core_cfg, self.game_cfg), + ChuniNewPlus(core_cfg, self.game_cfg), + ] + + self.logger = logging.getLogger("chuni") + + if not hasattr(self.logger, "inited"): + log_fmt_str = "[%(asctime)s] Chunithm | %(levelname)s | %(message)s" + log_fmt = logging.Formatter(log_fmt_str) + fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.core_cfg.server.log_dir, "chuni"), encoding='utf8', + when="d", backupCount=10) + + fileHandler.setFormatter(log_fmt) + + consoleHandler = logging.StreamHandler() + consoleHandler.setFormatter(log_fmt) + + self.logger.addHandler(fileHandler) + self.logger.addHandler(consoleHandler) + + self.logger.setLevel(self.game_cfg.server.loglevel) + coloredlogs.install(level=self.game_cfg.server.loglevel, logger=self.logger, fmt=log_fmt_str) + self.logger.inited = True + + def render_POST(self, request: Request, version: int, url_path: str) -> bytes: + req_raw = request.content.getvalue() + url_split = url_path.split("/") + encrtped = False + internal_ver = 0 + endpoint = url_split[len(url_split) - 1] + + if version < 105: # 1.0 + internal_ver = ChuniConstants.VER_CHUNITHM + elif version >= 105 and version < 110: # Plus + internal_ver = ChuniConstants.VER_CHUNITHM_PLUS + elif version >= 110 and version < 115: # Air + internal_ver = ChuniConstants.VER_CHUNITHM_AIR + elif version >= 115 and version < 120: # Air Plus + internal_ver = ChuniConstants.VER_CHUNITHM_AIR_PLUS + elif version >= 120 and version < 125: # Star + internal_ver = ChuniConstants.VER_CHUNITHM_STAR + elif version >= 125 and version < 130: # Star Plus + internal_ver = ChuniConstants.VER_CHUNITHM_STAR_PLUS + elif version >= 130 and version < 135: # Amazon + internal_ver = ChuniConstants.VER_CHUNITHM_AMAZON + elif version >= 135 and version < 140: # Amazon Plus + internal_ver = ChuniConstants.VER_CHUNITHM_AMAZON_PLUS + elif version >= 140 and version < 145: # Crystal + internal_ver = ChuniConstants.VER_CHUNITHM_CRYSTAL + elif version >= 145 and version < 150: # Crystal Plus + internal_ver = ChuniConstants.VER_CHUNITHM_CRYSTAL_PLUS + elif version >= 150 and version < 200: # Paradise + internal_ver = ChuniConstants.VER_CHUNITHM_PARADISE + elif version >= 200 and version < 205: # New + internal_ver = ChuniConstants.VER_CHUNITHM_NEW + elif version >= 205 and version < 210: # New Plus + internal_ver = ChuniConstants.VER_CHUNITHM_NEW_PLUS + + if all(c in string.hexdigits for c in endpoint) and len(endpoint) == 32: + # If we get a 32 character long hex string, it's a hash and we're + # doing encrypted. The likelyhood of false positives is low but + # technically not 0 + endpoint = request.getHeader("User-Agent").split("#")[0] + try: + crypt = AES.new( + bytes.fromhex(self.game_cfg.crypto.keys[str(internal_ver)][0]), + AES.MODE_CBC, + bytes.fromhex(self.game_cfg.crypto.keys[str(internal_ver)][1]) + ) + + req_raw = crypt.decrypt(req_raw) + + except: + self.logger.error(f"Failed to decrypt v{version} request to {endpoint} -> {req_raw}") + return zlib.compress("{\"stat\": \"0\"}".encode("utf-8")) + + encrtped = True + + if not encrtped and self.game_cfg.crypto.encrypted_only: + self.logger.error(f"Unencrypted v{version} {endpoint} request, but config is set to encrypted only: {req_raw}") + return zlib.compress("{\"stat\": \"0\"}".encode("utf-8")) + + try: + unzip = zlib.decompress(req_raw) + + except zlib.error as e: + self.logger.error(f"Failed to decompress v{version} {endpoint} request -> {e}") + return b"" + + req_data = json.loads(unzip) + + self.logger.info(f"v{version} {endpoint} request - {req_data}") + + func_to_find = "handle_" + inflection.underscore(endpoint) + "_request" + + try: + handler = getattr(self.versions[internal_ver], func_to_find) + 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: + self.logger.error(f"Error handling v{version} method {endpoint} - {e}") + return zlib.compress("{\"stat\": \"0\"}".encode("utf-8")) + + if resp == None: + resp = {'returnCode': 1} + + self.logger.info(f"Response {resp}") + + zipped = zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8")) + + if not encrtped: + return zipped + + padded = pad(zipped, 16) + + crypt = AES.new( + bytes.fromhex(self.game_cfg.crypto.keys[str(internal_ver)][0]), + AES.MODE_CBC, + bytes.fromhex(self.game_cfg.crypto.keys[str(internal_ver)][1]) + ) + + return crypt.encrypt(padded) diff --git a/titles/chuni/new.py b/titles/chuni/new.py new file mode 100644 index 0000000..909284e --- /dev/null +++ b/titles/chuni/new.py @@ -0,0 +1,128 @@ +import logging +from datetime import datetime, timedelta + +from typing import Dict + +from core.config import CoreConfig +from titles.chuni.const import ChuniConstants +from titles.chuni.database import ChuniData +from titles.chuni.base import ChuniBase +from titles.chuni.config import ChuniConfig + +class ChuniNew(ChuniBase): + + ITEM_TYPE = { + "character": 20, + "story": 21, + "card": 22 + } + + def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None: + self.core_cfg = core_cfg + self.game_cfg = game_cfg + self.data = ChuniData(core_cfg) + self.date_time_format = "%Y-%m-%d %H:%M:%S" + self.logger = logging.getLogger("chuni") + self.game = ChuniConstants.GAME_CODE + self.version = ChuniConstants.VER_CHUNITHM_NEW + + def handle_get_game_setting_api_request(self, data: Dict) -> Dict: + match_start = datetime.strftime(datetime.now() - timedelta(hours=10), self.date_time_format) + match_end = datetime.strftime(datetime.now() + timedelta(hours=10), self.date_time_format) + reboot_start = datetime.strftime(datetime.now() - timedelta(hours=11), self.date_time_format) + reboot_end = datetime.strftime(datetime.now() - timedelta(hours=10), self.date_time_format) + return { + "gameSetting": { + "isMaintenance": "false", + "requestInterval": 10, + "rebootStartTime": reboot_start, + "rebootEndTime": reboot_end, + "isBackgroundDistribute": "false", + "maxCountCharacter": 300, + "maxCountItem": 300, + "maxCountMusic": 300, + "matchStartTime": match_start, + "matchEndTime": match_end, + "matchTimeLimit": 99, + "matchErrorLimit": 9999, + "romVersion": "2.00.00", + "dataVersion": "2.00.00", + "matchingUri": f"http://{self.core_cfg.server.hostname}:{self.core_cfg.title.port}/SDHD/200/ChuniServlet/", + "matchingUriX": f"http://{self.core_cfg.server.hostname}:{self.core_cfg.title.port}/SDHD/200/ChuniServlet/", + "udpHolePunchUri": f"http://{self.core_cfg.server.hostname}:{self.core_cfg.title.port}/SDHD/200/ChuniServlet/", + "reflectorUri": f"http://{self.core_cfg.server.hostname}:{self.core_cfg.title.port}/SDHD/200/ChuniServlet/", + }, + "isDumpUpload": "false", + "isAou": "false", + } + + def handle_delete_token_api_request(self, data: Dict) -> Dict: + return { "returnCode": "1" } + + def handle_create_token_api_request(self, data: Dict) -> Dict: + return { "returnCode": "1" } + + def handle_get_user_map_area_api_request(self, data: Dict) -> Dict: + user_map_areas = self.data.item.get_map_areas(data["userId"]) + + map_areas = [] + for map_area in user_map_areas: + tmp = map_area._asdict() + tmp.pop("id") + tmp.pop("user") + map_areas.append(tmp) + + return { + "userId": data["userId"], + "userMapAreaList": map_areas + } + + def handle_get_user_symbol_chat_setting_api_request(self, data: Dict) -> Dict: + return { + "userId": data["userId"], + "symbolCharInfoList": [] + } + + def handle_get_user_preview_api_request(self, data: Dict) -> Dict: + profile = self.data.profile.get_profile_preview(data["userId"], self.version) + if profile is None: return None + profile_character = self.data.item.get_character(data["userId"], profile["characterId"]) + + if profile_character is None: + chara = {} + else: + chara = profile_character._asdict() + chara.pop("id") + chara.pop("user") + + data1 = { + "userId": data["userId"], + # Current Login State + "isLogin": False, + "lastLoginDate": profile["lastPlayDate"], + # User Profile + "userName": profile["userName"], + "reincarnationNum": profile["reincarnationNum"], + "level": profile["level"], + "exp": profile["exp"], + "playerRating": profile["playerRating"], + "lastGameId": profile["lastGameId"], + "lastRomVersion": profile["lastRomVersion"], + "lastDataVersion": profile["lastDataVersion"], + "lastPlayDate": profile["lastPlayDate"], + "emoneyBrandId": 0, + "trophyId": profile["trophyId"], + # Current Selected Character + "userCharacter": chara, + # User Game Options + "playerLevel": profile["playerLevel"], + "rating": profile["rating"], + "headphone": profile["headphone"], + "chargeState": 0, + "userNameEx": "0", + "banState": 0, + "classEmblemMedal": profile["classEmblemMedal"], + "classEmblemBase": profile["classEmblemBase"], + "battleRankId": profile["battleRankId"], + } + return data1 diff --git a/titles/chuni/newplus.py b/titles/chuni/newplus.py new file mode 100644 index 0000000..c048f8a --- /dev/null +++ b/titles/chuni/newplus.py @@ -0,0 +1,23 @@ +from datetime import datetime, timedelta +from typing import Dict, Any +import pytz + +from core.config import CoreConfig +from titles.chuni.new import ChuniNew +from titles.chuni.const import ChuniConstants +from titles.chuni.config import ChuniConfig + +class ChuniNewPlus(ChuniNew): + def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None: + super().__init__(core_cfg, game_cfg) + self.version = ChuniConstants.VER_CHUNITHM_NEW_PLUS + + def handle_get_game_setting_api_request(self, data: Dict) -> Dict: + ret = super().handle_get_game_setting_api_request(data) + ret["gameSetting"]["romVersion"] = "2.05.00" + ret["gameSetting"]["dataVersion"] = "2.05.00" + ret["gameSetting"]["matchingUri"] = f"http://{self.core_cfg.server.hostname}:{self.core_cfg.title.port}/SDHD/205/ChuniServlet/" + ret["gameSetting"]["matchingUriX"] = f"http://{self.core_cfg.server.hostname}:{self.core_cfg.title.port}/SDHD/205/ChuniServlet/" + ret["gameSetting"]["udpHolePunchUri"] = f"http://{self.core_cfg.server.hostname}:{self.core_cfg.title.port}/SDHD/205/ChuniServlet/" + ret["gameSetting"]["reflectorUri"] = f"http://{self.core_cfg.server.hostname}:{self.core_cfg.title.port}/SDHD/205/ChuniServlet/" + return ret diff --git a/titles/chuni/paradise.py b/titles/chuni/paradise.py new file mode 100644 index 0000000..19e92ca --- /dev/null +++ b/titles/chuni/paradise.py @@ -0,0 +1,18 @@ +from datetime import datetime, timedelta +from typing import Dict, Any +import pytz + +from core.config import CoreConfig +from titles.chuni.base import ChuniBase +from titles.chuni.const import ChuniConstants +from titles.chuni.config import ChuniConfig + +class ChuniParadise(ChuniBase): + def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None: + super().__init__(core_cfg, game_cfg) + self.version = ChuniConstants.VER_CHUNITHM_PARADISE + + def handle_get_game_setting_api_request(self, data: Dict) -> Dict: + ret = super().handle_get_game_setting_api_request(data) + ret["gameSetting"]["dataVersion"] = "1.50.00" + return ret diff --git a/titles/chuni/plus.py b/titles/chuni/plus.py new file mode 100644 index 0000000..492d4f6 --- /dev/null +++ b/titles/chuni/plus.py @@ -0,0 +1,16 @@ +from typing import Dict + +from core.config import CoreConfig +from titles.chuni.base import ChuniBase +from titles.chuni.const import ChuniConstants +from titles.chuni.config import ChuniConfig + +class ChuniPlus(ChuniBase): + def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None: + super().__init__(core_cfg, game_cfg) + self.version = ChuniConstants.VER_CHUNITHM_PLUS + + def handle_get_game_setting_api_request(self, data: Dict) -> Dict: + ret = super().handle_get_game_setting_api_request(data) + ret["gameSetting"]["dataVersion"] = "1.05.00" + return ret \ No newline at end of file diff --git a/titles/chuni/read.py b/titles/chuni/read.py new file mode 100644 index 0000000..dd67c03 --- /dev/null +++ b/titles/chuni/read.py @@ -0,0 +1,157 @@ +from typing import Optional +from os import walk, path +import xml.etree.ElementTree as ET +from read import BaseReader + +from core.config import CoreConfig +from titles.chuni.database import ChuniData +from titles.chuni.const import ChuniConstants + +class ChuniReader(BaseReader): + def __init__(self, config: CoreConfig, version: int, bin_dir: Optional[str], opt_dir: Optional[str], extra: Optional[str]) -> None: + super().__init__(config, version, bin_dir, opt_dir, extra) + self.data = ChuniData(config) + + try: + self.logger.info(f"Start importer for {ChuniConstants.game_ver_to_string(version)}") + except IndexError: + self.logger.error(f"Invalid chunithm version {version}") + exit(1) + + def read(self) -> None: + data_dirs = [] + if self.bin_dir is not None: + data_dirs += self.get_data_directories(self.bin_dir) + + if self.opt_dir is not None: + data_dirs += self.get_data_directories(self.opt_dir) + + for dir in data_dirs: + self.logger.info(f"Read from {dir}") + self.read_events(f"{dir}/event") + self.read_music(f"{dir}/music") + self.read_charges(f"{dir}/chargeItem") + self.read_avatar(f"{dir}/avatarAccessory") + + def read_events(self, evt_dir: str) -> None: + for root, dirs, files in walk(evt_dir): + for dir in dirs: + if path.exists(f"{root}/{dir}/Event.xml"): + with open(f"{root}/{dir}/Event.xml", 'rb') as fp: + bytedata = fp.read() + strdata = bytedata.decode('UTF-8') + + xml_root = ET.fromstring(strdata) + for name in xml_root.findall('name'): + id = name.find('id').text + name = name.find('str').text + for substances in xml_root.findall('substances'): + event_type = substances.find('type').text + + result = self.data.static.put_event(self.version, id, event_type, name) + if result is not None: + self.logger.info(f"Inserted event {id}") + else: + self.logger.warn(f"Failed to insert event {id}") + + def read_music(self, music_dir: str) -> None: + for root, dirs, files in walk(music_dir): + for dir in dirs: + if path.exists(f"{root}/{dir}/Music.xml"): + with open(f"{root}/{dir}/Music.xml", 'rb') as fp: + bytedata = fp.read() + strdata = bytedata.decode('UTF-8') + + xml_root = ET.fromstring(strdata) + for name in xml_root.findall('name'): + song_id = name.find('id').text + title = name.find('str').text + + for artistName in xml_root.findall('artistName'): + artist = artistName.find('str').text + + for genreNames in xml_root.findall('genreNames'): + for list_ in genreNames.findall('list'): + for StringID in list_.findall('StringID'): + genre = StringID.find('str').text + + for jaketFile in xml_root.findall('jaketFile'): #nice typo, SEGA + jacket_path = jaketFile.find('path').text + + for fumens in xml_root.findall('fumens'): + for MusicFumenData in fumens.findall('MusicFumenData'): + fumen_path = MusicFumenData.find('file').find("path") + + if fumen_path.text is not None: + chart_id = MusicFumenData.find('type').find('id').text + if chart_id == "4": + level = float(xml_root.find("starDifType").text) + we_chara = xml_root.find("worldsEndTagName").find("str").text + else: + level = float(f"{MusicFumenData.find('level').text}.{MusicFumenData.find('levelDecimal').text}") + we_chara = None + + result = self.data.static.put_music( + self.version, + song_id, + chart_id, + title, + artist, + level, + genre, + jacket_path, + we_chara + ) + + if result is not None: + self.logger.info(f"Inserted music {song_id} chart {chart_id}") + else: + self.logger.warn(f"Failed to insert music {song_id} chart {chart_id}") + + def read_charges(self, charge_dir: str) -> None: + for root, dirs, files in walk(charge_dir): + for dir in dirs: + if path.exists(f"{root}/{dir}/ChargeItem.xml"): + with open(f"{root}/{dir}/ChargeItem.xml", 'rb') as fp: + bytedata = fp.read() + strdata = bytedata.decode('UTF-8') + + xml_root = ET.fromstring(strdata) + for name in xml_root.findall('name'): + id = name.find('id').text + name = name.find('str').text + expirationDays = xml_root.find('expirationDays').text + consumeType = xml_root.find('consumeType').text + sellingAppeal = bool(xml_root.find('sellingAppeal').text) + + result = self.data.static.put_charge(self.version, id, name, expirationDays, consumeType, sellingAppeal) + + if result is not None: + self.logger.info(f"Inserted charge {id}") + else: + self.logger.warn(f"Failed to insert charge {id}") + + def read_avatar(self, avatar_dir: str) -> None: + for root, dirs, files in walk(avatar_dir): + for dir in dirs: + if path.exists(f"{root}/{dir}/AvatarAccessory.xml"): + with open(f"{root}/{dir}/AvatarAccessory.xml", 'rb') as fp: + bytedata = fp.read() + strdata = bytedata.decode('UTF-8') + + xml_root = ET.fromstring(strdata) + for name in xml_root.findall('name'): + id = name.find('id').text + name = name.find('str').text + category = xml_root.find('category').text + for image in xml_root.findall('image'): + iconPath = image.find('path').text + for texture in xml_root.findall('texture'): + texturePath = texture.find('path').text + + result = self.data.static.put_avatar(self.version, id, name, category, iconPath, texturePath) + + if result is not None: + self.logger.info(f"Inserted avatarAccessory {id}") + else: + self.logger.warn(f"Failed to insert avatarAccessory {id}") diff --git a/titles/chuni/schema/__init__.py b/titles/chuni/schema/__init__.py new file mode 100644 index 0000000..18c408e --- /dev/null +++ b/titles/chuni/schema/__init__.py @@ -0,0 +1,6 @@ +from titles.chuni.schema.profile import ChuniProfileData +from titles.chuni.schema.score import ChuniScoreData +from titles.chuni.schema.item import ChuniItemData +from titles.chuni.schema.static import ChuniStaticData + +__all__ = ["ChuniProfileData", "ChuniScoreData", "ChuniItemData", "ChuniStaticData"] \ No newline at end of file diff --git a/titles/chuni/schema/item.py b/titles/chuni/schema/item.py new file mode 100644 index 0000000..6973558 --- /dev/null +++ b/titles/chuni/schema/item.py @@ -0,0 +1,207 @@ +from typing import Dict, List, Optional +from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_ +from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON +from sqlalchemy.engine.base import Connection +from sqlalchemy.schema import ForeignKey +from sqlalchemy.sql import func, select +from sqlalchemy.dialects.mysql import insert +from sqlalchemy.engine import Row + +from core.data.schema import BaseData, metadata + +character = Table( + "chuni_item_character", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("characterId", Integer), + Column("level", Integer), + Column("param1", Integer), + Column("param2", Integer), + Column("isValid", Boolean), + Column("skillId", Integer), + Column("isNewMark", Boolean), + Column("playCount", Integer), + Column("friendshipExp", Integer), + Column("assignIllust", Integer), + Column("exMaxLv", Integer), + UniqueConstraint("user", "characterId", name="chuni_item_character_uk"), + mysql_charset='utf8mb4' +) + +item = Table( + "chuni_item_item", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("itemId", Integer), + Column("itemKind", Integer), + Column("stock", Integer), + Column("isValid", Boolean), + UniqueConstraint("user", "itemId", "itemKind", name="chuni_item_item_uk"), + mysql_charset='utf8mb4' +) + +duel = Table( + "chuni_item_duel", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("duelId", Integer), + Column("progress", Integer), + Column("point", Integer), + Column("isClear", Boolean), + Column("lastPlayDate", String(25)), + Column("param1", Integer), + Column("param2", Integer), + Column("param3", Integer), + Column("param4", Integer), + UniqueConstraint("user", "duelId", name="chuni_item_duel_uk"), + mysql_charset='utf8mb4' +) + +map = Table( + "chuni_item_map", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("mapId", Integer), + Column("position", Integer), + Column("isClear", Boolean), + Column("areaId", Integer), + Column("routeNumber", Integer), + Column("eventId", Integer), + Column("rate", Integer), + Column("statusCount", Integer), + Column("isValid", Boolean), + UniqueConstraint("user", "mapId", name="chuni_item_map_uk"), + mysql_charset='utf8mb4' +) + +map_area = Table( + "chuni_item_map_area", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("mapAreaId", Integer), + Column("rate", Integer), + Column("isClear", Boolean), + Column("isLocked", Boolean), + Column("position", Integer), + Column("statusCount", Integer), + Column("remainGridCount", Integer), + UniqueConstraint("user", "mapAreaId", name="chuni_item_map_area_uk"), + mysql_charset='utf8mb4' +) + +class ChuniItemData(BaseData): + def put_character(self, user_id: int, character_data: Dict) -> Optional[int]: + character_data["user"] = user_id + + character_data = self.fix_bools(character_data) + + sql = insert(character).values(**character_data) + conflict = sql.on_duplicate_key_update(**character_data) + + result = self.execute(conflict) + if result is None: return None + return result.lastrowid + + def get_character(self, user_id: int, character_id: int) -> Optional[Dict]: + sql = select(character).where(and_( + character.c.user == user_id, + character.c.characterId == character_id + )) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() + + def get_characters(self, user_id: int) -> Optional[List[Row]]: + sql = select(character).where(character.c.user == user_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def put_item(self, user_id: int, item_data: Dict) -> Optional[int]: + item_data["user"] = user_id + + item_data = self.fix_bools(item_data) + + sql = insert(item).values(**item_data) + conflict = sql.on_duplicate_key_update(**item_data) + + result = self.execute(conflict) + if result is None: return None + return result.lastrowid + + def get_items(self, user_id: int, kind: int = None) -> Optional[List[Row]]: + if kind is None: + sql = select(item).where(item.c.user == user_id) + else: + sql = select(item).where(and_( + item.c.user == user_id, + item.c.itemKind == kind + )) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def put_duel(self, user_id: int, duel_data: Dict) -> Optional[int]: + duel_data["user"] = user_id + + duel_data = self.fix_bools(duel_data) + + sql = insert(duel).values(**duel_data) + conflict = sql.on_duplicate_key_update(**duel_data) + + result = self.execute(conflict) + if result is None: return None + return result.lastrowid + + def get_duels(self, user_id: int) -> Optional[List[Row]]: + sql = select(duel).where(duel.c.user == user_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def put_map(self, user_id: int, map_data: Dict) -> Optional[int]: + map_data["user"] = user_id + + map_data = self.fix_bools(map_data) + + sql = insert(map).values(**map_data) + conflict = sql.on_duplicate_key_update(**map_data) + + result = self.execute(conflict) + if result is None: return None + return result.lastrowid + + def get_maps(self, user_id: int) -> Optional[List[Row]]: + sql = select(map).where(map.c.user == user_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def put_map_area(self, user_id: int, map_area_data: Dict) -> Optional[int]: + map_area_data["user"] = user_id + + map_area_data = self.fix_bools(map_area_data) + + sql = insert(map_area).values(**map_area_data) + conflict = sql.on_duplicate_key_update(**map_area_data) + + result = self.execute(conflict) + if result is None: return None + return result.lastrowid + + def get_map_areas(self, user_id: int) -> Optional[List[Row]]: + sql = select(map_area).where(map_area.c.user == user_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() \ No newline at end of file diff --git a/titles/chuni/schema/profile.py b/titles/chuni/schema/profile.py new file mode 100644 index 0000000..f23f54d --- /dev/null +++ b/titles/chuni/schema/profile.py @@ -0,0 +1,551 @@ +from typing import Dict, List, Optional +from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_ +from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, BigInteger +from sqlalchemy.engine.base import Connection +from sqlalchemy.schema import ForeignKey +from sqlalchemy.engine import Row +from sqlalchemy.sql import func, select +from sqlalchemy.dialects.mysql import insert + +from core.data.schema import BaseData, metadata + +profile = Table( + "chuni_profile_data", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("version", Integer, nullable=False), + Column("exp", Integer), + Column("level", Integer), + Column("point", Integer), + Column("frameId", Integer), + Column("isMaimai", Boolean), + Column("trophyId", Integer), + Column("userName", String(25)), + Column("isWebJoin", Boolean), + Column("playCount", Integer), + Column("lastGameId", String(25)), + Column("totalPoint", BigInteger), + Column("characterId", Integer), + Column("firstGameId", String(25)), + Column("friendCount", Integer), + Column("lastPlaceId", Integer), + Column("nameplateId", Integer), + Column("totalMapNum", Integer), + Column("lastAllNetId", Integer), + Column("lastClientId", String(25)), + Column("lastPlayDate", String(25)), + Column("lastRegionId", Integer), + Column("playerRating", Integer), + Column("totalHiScore", Integer), + Column("webLimitDate", String(25)), + Column("firstPlayDate", String(25)), + Column("highestRating", Integer), + Column("lastPlaceName", String(25)), + Column("multiWinCount", Integer), + Column("acceptResCount", Integer), + Column("lastRegionName", String(25)), + Column("lastRomVersion", String(25)), + Column("multiPlayCount", Integer), + Column("firstRomVersion", String(25)), + Column("lastDataVersion", String(25)), + Column("requestResCount", Integer), + Column("successResCount", Integer), + Column("eventWatchedDate", String(25)), + Column("firstDataVersion", String(25)), + Column("reincarnationNum", Integer), + Column("playedTutorialBit", Integer), + Column("totalBasicHighScore", Integer), + Column("totalExpertHighScore", Integer), + Column("totalMasterHighScore", Integer), + Column("totalRepertoireCount", Integer), + Column("firstTutorialCancelNum", Integer), + Column("totalAdvancedHighScore", Integer), + Column("masterTutorialCancelNum", Integer), + Column("ext1", Integer), # Added in chunew + Column("ext2", Integer), + Column("ext3", Integer), + Column("ext4", Integer), + Column("ext5", Integer), + Column("ext6", Integer), + Column("ext7", Integer), + Column("ext8", Integer), + Column("ext9", Integer), + Column("ext10", Integer), + Column("extStr1", String(255)), + Column("extStr2", String(255)), + Column("extLong1", Integer), + Column("extLong2", Integer), + Column("mapIconId", Integer), + Column("compatibleCmVersion", String(25)), + Column("medal", Integer), + Column("voiceId", Integer), + Column("teamId", Integer, ForeignKey("chuni_profile_team.id", ondelete="SET NULL", onupdate="SET NULL")), + Column("avatarBack", Integer, server_default="0"), + Column("avatarFace", Integer, server_default="0"), + Column("eliteRankPoint", Integer, server_default="0"), + Column("stockedGridCount", Integer, server_default="0"), + Column("netBattleLoseCount", Integer, server_default="0"), + Column("netBattleHostErrCnt", Integer, server_default="0"), + Column("netBattle4thCount", Integer, server_default="0"), + Column("overPowerRate", Integer, server_default="0"), + Column("battleRewardStatus", Integer, server_default="0"), + Column("avatarPoint", Integer, server_default="0"), + Column("netBattle1stCount", Integer, server_default="0"), + Column("charaIllustId", Integer, server_default="0"), + Column("avatarItem", Integer, server_default="0"), + Column("userNameEx", String(8), server_default=""), + Column("netBattleWinCount", Integer, server_default="0"), + Column("netBattleCorrection", Integer, server_default="0"), + Column("classEmblemMedal", Integer, server_default="0"), + Column("overPowerPoint", Integer, server_default="0"), + Column("netBattleErrCnt", Integer, server_default="0"), + Column("battleRankId", Integer, server_default="0"), + Column("netBattle3rdCount", Integer, server_default="0"), + Column("netBattleConsecutiveWinCount", Integer, server_default="0"), + Column("overPowerLowerRank", Integer, server_default="0"), + Column("avatarWear", Integer, server_default="0"), + Column("classEmblemBase", Integer, server_default="0"), + Column("battleRankPoint", Integer, server_default="0"), + Column("netBattle2ndCount", Integer, server_default="0"), + Column("totalUltimaHighScore", Integer, server_default="0"), + Column("skillId", Integer, server_default="0"), + Column("lastCountryCode", String(5), server_default="JPN"), + Column("isNetBattleHost", Boolean, server_default="0"), + Column("avatarFront", Integer, server_default="0"), + Column("avatarSkin", Integer, server_default="0"), + Column("battleRewardCount", Integer, server_default="0"), + Column("battleRewardIndex", Integer, server_default="0"), + Column("netBattlePlayCount", Integer, server_default="0"), + Column("exMapLoopCount", Integer, server_default="0"), + Column("netBattleEndState", Integer, server_default="0"), + Column("avatarHead", Integer, server_default="0"), + UniqueConstraint("user", "version", name="chuni_profile_profile_uk"), + mysql_charset='utf8mb4' +) + +profile_ex = Table( + "chuni_profile_data_ex", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("version", Integer, nullable=False), + Column("ext1", Integer), + Column("ext2", Integer), + Column("ext3", Integer), + Column("ext4", Integer), + Column("ext5", Integer), + Column("ext6", Integer), + Column("ext7", Integer), + Column("ext8", Integer), + Column("ext9", Integer), + Column("ext10", Integer), + Column("ext11", Integer), + Column("ext12", Integer), + Column("ext13", Integer), + Column("ext14", Integer), + Column("ext15", Integer), + Column("ext16", Integer), + Column("ext17", Integer), + Column("ext18", Integer), + Column("ext19", Integer), + Column("ext20", Integer), + Column("medal", Integer), + Column("extStr1", String(255)), + Column("extStr2", String(255)), + Column("extStr3", String(255)), + Column("extStr4", String(255)), + Column("extStr5", String(255)), + Column("voiceId", Integer), + Column("extLong1", Integer), + Column("extLong2", Integer), + Column("extLong3", Integer), + Column("extLong4", Integer), + Column("extLong5", Integer), + Column("mapIconId", Integer), + Column("compatibleCmVersion", String(25)), + UniqueConstraint("user", "version", name="chuni_profile_data_ex_uk"), + mysql_charset='utf8mb4' +) + +option = Table( + "chuni_profile_option", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("speed", Integer), + Column("bgInfo", Integer), + Column("rating", Integer), + Column("privacy", Integer), + Column("judgePos", Integer), + Column("matching", Integer), + Column("guideLine", Integer), + Column("headphone", Integer), + Column("optionSet", Integer), + Column("fieldColor", Integer), + Column("guideSound", Integer), + Column("successAir", Integer), + Column("successTap", Integer), + Column("judgeAttack", Integer), + Column("playerLevel", Integer), + Column("soundEffect", Integer), + Column("judgeJustice", Integer), + Column("successExTap", Integer), + Column("successFlick", Integer), + Column("successSkill", Integer), + Column("successSlideHold", Integer), + Column("successTapTimbre", Integer), + Column("ext1", Integer), # Added in chunew + Column("ext2", Integer), + Column("ext3", Integer), + Column("ext4", Integer), + Column("ext5", Integer), + Column("ext6", Integer), + Column("ext7", Integer), + Column("ext8", Integer), + Column("ext9", Integer), + Column("ext10", Integer), + Column("categoryDetail", Integer, server_default="0"), + Column("judgeTimingOffset_120", Integer, server_default="0"), + Column("resultVoiceShort", Integer, server_default="0"), + Column("judgeAppendSe", Integer, server_default="0"), + Column("judgeCritical", Integer, server_default="0"), + Column("trackSkip", Integer, server_default="0"), + Column("selectMusicFilterLv", Integer, server_default="0"), + Column("sortMusicFilterLv", Integer, server_default="0"), + Column("sortMusicGenre", Integer, server_default="0"), + Column("speed_120", Integer, server_default="0"), + Column("judgeTimingOffset", Integer, server_default="0"), + Column("mirrorFumen", Integer, server_default="0"), + Column("playTimingOffset_120", Integer, server_default="0"), + Column("hardJudge", Integer, server_default="0"), + Column("notesThickness", Integer, server_default="0"), + Column("fieldWallPosition", Integer, server_default="0"), + Column("playTimingOffset", Integer, server_default="0"), + Column("fieldWallPosition_120", Integer, server_default="0"), + UniqueConstraint("user", name="chuni_profile_option_uk"), + mysql_charset='utf8mb4' +) + +option_ex = Table( + "chuni_profile_option_ex", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("ext1", Integer), + Column("ext2", Integer), + Column("ext3", Integer), + Column("ext4", Integer), + Column("ext5", Integer), + Column("ext6", Integer), + Column("ext7", Integer), + Column("ext8", Integer), + Column("ext9", Integer), + Column("ext10", Integer), + Column("ext11", Integer), + Column("ext12", Integer), + Column("ext13", Integer), + Column("ext14", Integer), + Column("ext15", Integer), + Column("ext16", Integer), + Column("ext17", Integer), + Column("ext18", Integer), + Column("ext19", Integer), + Column("ext20", Integer), + UniqueConstraint("user", name="chuni_profile_option_ex_uk"), + mysql_charset='utf8mb4' +) + +recent_rating = Table( + "chuni_profile_recent_rating", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("recentRating", JSON), + UniqueConstraint("user", name="chuni_profile_recent_rating_uk"), + mysql_charset='utf8mb4' +) + +region = Table( + "chuni_profile_region", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("regionId", Integer), + Column("playCount", Integer), + UniqueConstraint("user", "regionId", name="chuni_profile_region_uk"), + mysql_charset='utf8mb4' +) + +activity = Table( + "chuni_profile_activity", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("kind", Integer), + Column("activityId", Integer), # Reminder: Change this to ID in base.py or the game will be sad + Column("sortNumber", Integer), + Column("param1", Integer), + Column("param2", Integer), + Column("param3", Integer), + Column("param4", Integer), + UniqueConstraint("user", "kind", "activityId", name="chuni_profile_activity_uk"), + mysql_charset='utf8mb4' +) + +charge = Table( + "chuni_profile_charge", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("chargeId", Integer), + Column("stock", Integer), + Column("purchaseDate", String(25)), + Column("validDate", String(25)), + Column("param1", Integer), + Column("param2", Integer), + Column("paramDate", String(25)), + UniqueConstraint("user", "chargeId", name="chuni_profile_charge_uk"), + mysql_charset='utf8mb4' +) + +emoney = Table( + "chuni_profile_emoney", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("ext1", Integer), + Column("ext2", Integer), + Column("ext3", Integer), + Column("type", Integer), + Column("emoneyBrand", Integer), + Column("emoneyCredit", Integer), + UniqueConstraint("user", "emoneyBrand", name="chuni_profile_emoney_uk"), + mysql_charset='utf8mb4' +) + +overpower = Table( + "chuni_profile_overpower", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("genreId", Integer), + Column("difficulty", Integer), + Column("rate", Integer), + Column("point", Integer), + UniqueConstraint("user", "genreId", "difficulty", name="chuni_profile_emoney_uk"), + mysql_charset='utf8mb4' +) + +team = Table( + "chuni_profile_team", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("teamName", String(255)), + Column("teamPoint", Integer), + mysql_charset='utf8mb4' +) + +class ChuniProfileData(BaseData): + def put_profile_data(self, aime_id: int, version: int, profile_data: Dict) -> Optional[int]: + profile_data["user"] = aime_id + profile_data["version"] = version + if "accessCode" in profile_data: + profile_data.pop("accessCode") + + profile_data = self.fix_bools(profile_data) + + sql = insert(profile).values(**profile_data) + conflict = sql.on_duplicate_key_update(**profile_data) + result = self.execute(conflict) + + if result is None: + self.logger.warn(f"put_profile_data: Failed to update! aime_id: {aime_id}") + return None + return result.lastrowid + + def get_profile_preview(self, aime_id: int, version: int) -> Optional[Row]: + sql = select([profile, option]).join(option, profile.c.user == option.c.user).filter( + and_(profile.c.user == aime_id, profile.c.version == version) + ) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() + + def get_profile_data(self, aime_id: int, version: int) -> Optional[Row]: + sql = select(profile).where(and_( + profile.c.user == aime_id, + profile.c.version == version, + )) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() + + def put_profile_data_ex(self, aime_id: int, version: int, profile_ex_data: Dict) -> Optional[int]: + profile_ex_data["user"] = aime_id + profile_ex_data["version"] = version + if "accessCode" in profile_ex_data: + profile_ex_data.pop("accessCode") + + sql = insert(profile_ex).values(**profile_ex_data) + conflict = sql.on_duplicate_key_update(**profile_ex_data) + result = self.execute(conflict) + + if result is None: + self.logger.warn(f"put_profile_data_ex: Failed to update! aime_id: {aime_id}") + return None + return result.lastrowid + + def get_profile_data_ex(self, aime_id: int, version: int) -> Optional[Row]: + sql = select(profile_ex).where(and_( + profile_ex.c.user == aime_id, + profile_ex.c.version == version, + )) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() + + def put_profile_option(self, aime_id: int, option_data: Dict) -> Optional[int]: + option_data["user"] = aime_id + + sql = insert(option).values(**option_data) + conflict = sql.on_duplicate_key_update(**option_data) + result = self.execute(conflict) + + if result is None: + self.logger.warn(f"put_profile_option: Failed to update! aime_id: {aime_id}") + return None + return result.lastrowid + + def get_profile_option(self, aime_id: int) -> Optional[Row]: + sql = select(option).where(option.c.user == aime_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() + + def put_profile_option_ex(self, aime_id: int, option_ex_data: Dict) -> Optional[int]: + option_ex_data["user"] = aime_id + + sql = insert(option_ex).values(**option_ex_data) + conflict = sql.on_duplicate_key_update(**option_ex_data) + result = self.execute(conflict) + + if result is None: + self.logger.warn(f"put_profile_option_ex: Failed to update! aime_id: {aime_id}") + return None + return result.lastrowid + + def get_profile_option_ex(self, aime_id: int) -> Optional[Row]: + sql = select(option_ex).where(option_ex.c.user == aime_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() + + def put_profile_recent_rating(self, aime_id: int, recent_rating_data: List[Dict]) -> Optional[int]: + sql = insert(recent_rating).values( + user = aime_id, + recentRating = recent_rating_data + ) + conflict = sql.on_duplicate_key_update(recentRating = recent_rating_data) + + result = self.execute(conflict) + if result is None: + self.logger.warn(f"put_profile_recent_rating: Failed to update! aime_id: {aime_id}") + return None + return result.lastrowid + + def get_profile_recent_rating(self, aime_id: int) -> Optional[Row]: + sql = select(recent_rating).where(recent_rating.c.user == aime_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() + + def put_profile_activity(self, aime_id: int, activity_data: Dict) -> Optional[int]: + # The game just uses "id" but we need to distinguish that from the db column "id" + activity_data["user"] = aime_id + activity_data["activityId"] = activity_data["id"] + activity_data.pop("id") + + sql = insert(activity).values(**activity_data) + conflict = sql.on_duplicate_key_update(**activity_data) + result = self.execute(conflict) + + if result is None: + self.logger.warn(f"put_profile_activity: Failed to update! aime_id: {aime_id}") + return None + return result.lastrowid + + def get_profile_activity(self, aime_id: int, kind: int) -> Optional[List[Row]]: + sql = select(activity).where(and_( + activity.c.user == aime_id, + activity.c.kind == kind + )) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def put_profile_charge(self, aime_id: int, charge_data: Dict) -> Optional[int]: + charge_data["user"] = aime_id + + sql = insert(charge).values(**charge_data) + conflict = sql.on_duplicate_key_update(**charge_data) + result = self.execute(conflict) + + if result is None: + self.logger.warn(f"put_profile_charge: Failed to update! aime_id: {aime_id}") + return None + return result.lastrowid + + def get_profile_charge(self, aime_id: int) -> Optional[List[Row]]: + sql = select(charge).where(charge.c.user == aime_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def add_profile_region(self, aime_id: int, region_id: int) -> Optional[int]: + pass + + def get_profile_regions(self, aime_id: int) -> Optional[List[Row]]: + pass + + def put_profile_emoney(self, aime_id: int, emoney_data: Dict) -> Optional[int]: + emoney_data["user"] = aime_id + + sql = insert(emoney).values(**emoney_data) + conflict = sql.on_duplicate_key_update(**emoney_data) + + result = self.execute(conflict) + if result is None: return None + return result.lastrowid + + def get_profile_emoney(self, aime_id: int) -> Optional[List[Row]]: + sql = select(emoney).where(emoney.c.user == aime_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def put_profile_overpower(self, aime_id: int, overpower_data: Dict) -> Optional[int]: + overpower_data["user"] = aime_id + + sql = insert(overpower).values(**overpower_data) + conflict = sql.on_duplicate_key_update(**overpower_data) + + result = self.execute(conflict) + if result is None: return None + return result.lastrowid + + def get_profile_overpower(self, aime_id: int) -> Optional[List[Row]]: + sql = select(overpower).where(overpower.c.user == aime_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() diff --git a/titles/chuni/schema/score.py b/titles/chuni/schema/score.py new file mode 100644 index 0000000..353401f --- /dev/null +++ b/titles/chuni/schema/score.py @@ -0,0 +1,178 @@ +from typing import Dict, List, Optional +from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_ +from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, BigInteger +from sqlalchemy.engine.base import Connection +from sqlalchemy.schema import ForeignKey +from sqlalchemy.engine import Row +from sqlalchemy.sql import func, select +from sqlalchemy.dialects.mysql import insert + +from core.data.schema import BaseData, metadata + +course = Table( + "chuni_score_course", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("courseId", Integer), + Column("classId", Integer), + Column("playCount", Integer), + Column("scoreMax", Integer), + Column("isFullCombo", Boolean), + Column("isAllJustice", Boolean), + Column("isSuccess", Boolean), + Column("scoreRank", Integer), + Column("eventId", Integer), + Column("lastPlayDate", String(25)), + Column("param1", Integer), + Column("param2", Integer), + Column("param3", Integer), + Column("param4", Integer), + Column("isClear", Boolean), + UniqueConstraint("user", "courseId", name="chuni_score_course_uk"), + mysql_charset='utf8mb4' +) + +best_score = Table( + "chuni_score_best", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("musicId", Integer), + Column("level", Integer), + Column("playCount", Integer), + Column("scoreMax", Integer), + Column("resRequestCount", Integer), + Column("resAcceptCount", Integer), + Column("resSuccessCount", Integer), + Column("missCount", Integer), + Column("maxComboCount", Integer), + Column("isFullCombo", Boolean), + Column("isAllJustice", Boolean), + Column("isSuccess", Boolean), + Column("fullChain", Integer), + Column("maxChain", Integer), + Column("scoreRank", Integer), + Column("isLock", Boolean), + Column("ext1", Integer), + Column("theoryCount", Integer), + UniqueConstraint("user", "musicId", "level", name="chuni_score_best_uk"), + mysql_charset='utf8mb4' +) + +playlog = Table( + "chuni_score_playlog", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("orderId", Integer), + Column("sortNumber", Integer), + Column("placeId", Integer), + Column("playDate", String(20)), + Column("userPlayDate", String(20)), + Column("musicId", Integer), + Column("level", Integer), + Column("customId", Integer), + Column("playedUserId1", Integer), + Column("playedUserId2", Integer), + Column("playedUserId3", Integer), + Column("playedUserName1", String(20)), + Column("playedUserName2", String(20)), + Column("playedUserName3", String(20)), + Column("playedMusicLevel1", Integer), + Column("playedMusicLevel2", Integer), + Column("playedMusicLevel3", Integer), + Column("playedCustom1", Integer), + Column("playedCustom2", Integer), + Column("playedCustom3", Integer), + Column("track", Integer), + Column("score", Integer), + Column("rank", Integer), + Column("maxCombo", Integer), + Column("maxChain", Integer), + Column("rateTap", Integer), + Column("rateHold", Integer), + Column("rateSlide", Integer), + Column("rateAir", Integer), + Column("rateFlick", Integer), + Column("judgeGuilty", Integer), + Column("judgeAttack", Integer), + Column("judgeJustice", Integer), + Column("judgeCritical", Integer), + Column("eventId", Integer), + Column("playerRating", Integer), + Column("isNewRecord", Boolean), + Column("isFullCombo", Boolean), + Column("fullChainKind", Integer), + Column("isAllJustice", Boolean), + Column("isContinue", Boolean), + Column("isFreeToPlay", Boolean), + Column("characterId", Integer), + Column("skillId", Integer), + Column("playKind", Integer), + Column("isClear", Boolean), + Column("skillLevel", Integer), + Column("skillEffect", Integer), + Column("placeName", String(255)), + Column("isMaimai", Boolean), + Column("commonId", Integer), + Column("charaIllustId", Integer), + Column("romVersion", String(255)), + Column("judgeHeaven", Integer), + mysql_charset='utf8mb4' +) + +class ChuniScoreData(BaseData): + def get_courses(self, aime_id: int) -> Optional[Row]: + sql = select(course).where(course.c.user == aime_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def put_course(self, aime_id: int, course_data: Dict) -> Optional[int]: + course_data["user"] = aime_id + course_data = self.fix_bools(course_data) + + sql = insert(course).values(**course_data) + conflict = sql.on_duplicate_key_update(**course_data) + + result = self.execute(conflict) + if result is None: return None + return result.lastrowid + + def get_scores(self, aime_id: int) -> Optional[Row]: + sql = select(best_score).where(best_score.c.user == aime_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def put_score(self, aime_id: int, score_data: Dict) -> Optional[int]: + score_data["user"] = aime_id + score_data = self.fix_bools(score_data) + + sql = insert(best_score).values(**score_data) + conflict = sql.on_duplicate_key_update(**score_data) + + result = self.execute(conflict) + if result is None: return None + return result.lastrowid + + def get_playlogs(self, aime_id: int) -> Optional[Row]: + sql = select(playlog).where(playlog.c.user == aime_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def put_playlog(self, aime_id: int, playlog_data: Dict) -> Optional[int]: + playlog_data["user"] = aime_id + playlog_data = self.fix_bools(playlog_data) + + sql = insert(playlog).values(**playlog_data) + conflict = sql.on_duplicate_key_update(**playlog_data) + + result = self.execute(conflict) + if result is None: return None + return result.lastrowid diff --git a/titles/chuni/schema/static.py b/titles/chuni/schema/static.py new file mode 100644 index 0000000..99dd3e8 --- /dev/null +++ b/titles/chuni/schema/static.py @@ -0,0 +1,223 @@ +from typing import Dict, List, Optional +from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_ +from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, Float +from sqlalchemy.engine.base import Connection +from sqlalchemy.engine import Row +from sqlalchemy.schema import ForeignKey +from sqlalchemy.sql import func, select +from sqlalchemy.dialects.mysql import insert + +from core.data.schema import BaseData, metadata + +events = Table( + "chuni_static_events", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("version", Integer, nullable=False), + Column("eventId", Integer), + Column("type", Integer), + Column("name", String(255)), + Column("enabled", Boolean, server_default="1"), + UniqueConstraint("version", "eventId", name="chuni_static_events_uk"), + mysql_charset='utf8mb4' +) + +music = Table( + "chuni_static_music", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("version", Integer, nullable=False), + Column("songId", Integer), + Column("chartId", Integer), + Column("title", String(255)), + Column("artist", String(255)), + Column("level", Float), + Column("genre", String(255)), + Column("jacketPath", String(255)), + Column("worldsEndTag", String(20)), + UniqueConstraint("version", "songId", "chartId", name="chuni_static_music_uk"), + mysql_charset='utf8mb4' +) + +charge = Table( + "chuni_static_charge", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("version", Integer, nullable=False), + Column("chargeId", Integer), + Column("name", String(255)), + Column("expirationDays", Integer), + Column("consumeType", Integer), + Column("sellingAppeal", Boolean), + Column("enabled", Boolean, server_default="1"), + UniqueConstraint("version", "chargeId", name="chuni_static_charge_uk"), + mysql_charset='utf8mb4' +) + +avatar = Table( + "chuni_static_avatar", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("version", Integer, nullable=False), + Column("avatarAccessoryId", Integer), + Column("name", String(255)), + Column("category", Integer), + Column("iconPath", String(255)), + Column("texturePath", String(255)), + UniqueConstraint("version", "avatarAccessoryId", name="chuni_static_avatar_uk"), + mysql_charset='utf8mb4' +) + +class ChuniStaticData(BaseData): + def put_event(self, version: int, event_id: int, type: int, name: str) -> Optional[int]: + sql = insert(events).values( + version = version, + eventId = event_id, + type = type, + name = name + ) + + conflict = sql.on_duplicate_key_update( + name = name + ) + + result = self.execute(conflict) + if result is None: return None + return result.lastrowid + + def update_event(self, version: int, event_id: int, enabled: bool) -> Optional[bool]: + sql = events.update(and_(events.c.version == version, events.c.eventId == event_id)).values( + enabled = enabled + ) + + result = self.execute(sql) + if result is None: + self.logger.warn(f"update_event: failed to update event! version: {version}, event_id: {event_id}, enabled: {enabled}") + return None + + event = self.get_event(version, event_id) + if event is None: + self.logger.warn(f"update_event: failed to fetch event {event_id} after updating") + return None + return event["enabled"] + + def get_event(self, version: int, event_id: int) -> Optional[Row]: + sql = select(events).where(and_(events.c.version == version, events.c.eventId == event_id)) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() + + def get_enabled_events(self, version: int) -> Optional[List[Row]]: + sql = select(events).where(and_(events.c.version == version, events.c.enabled == True)) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def get_events(self, version: int) -> Optional[List[Row]]: + sql = select(events).where(events.c.version == version) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def put_music(self, version: int, song_id: int, chart_id: int, title: int, artist: str, + level: float, genre: str, jacketPath: str, we_tag: str) -> Optional[int]: + + sql = insert(music).values( + version = version, + songId = song_id, + chartId = chart_id, + title = title, + artist = artist, + level = level, + genre = genre, + jacketPath = jacketPath, + worldsEndTag = we_tag, + ) + + conflict = sql.on_duplicate_key_update( + title = title, + artist = artist, + level = level, + genre = genre, + jacketPath = jacketPath, + worldsEndTag = we_tag, + ) + + result = self.execute(conflict) + if result is None: return None + return result.lastrowid + + def put_charge(self, version: int, charge_id: int, name: str, expiration_days: int, + consume_type: int, selling_appeal: bool) -> Optional[int]: + sql = insert(charge).values( + version = version, + chargeId = charge_id, + name = name, + expirationDays = expiration_days, + consumeType = consume_type, + sellingAppeal = selling_appeal, + ) + + conflict = sql.on_duplicate_key_update( + name = name, + expirationDays = expiration_days, + consumeType = consume_type, + sellingAppeal = selling_appeal, + ) + + result = self.execute(conflict) + if result is None: return None + return result.lastrowid + + def get_enabled_charges(self, version: int) -> Optional[List[Row]]: + sql = select(charge).where(and_( + charge.c.version == version, + charge.c.enabled == True + )) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def get_charges(self, version: int) -> Optional[List[Row]]: + sql = select(charge).where(charge.c.version == version) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def get_music_chart(self, version: int, song_id: int, chart_id: int) -> Optional[List[Row]]: + sql = select(music).where(and_( + music.c.version == version, + music.c.songId == song_id, + music.c.chartId == chart_id + )) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() + + def put_avatar(self, version: int, avatarAccessoryId: int, name: str, category: int, iconPath: str, texturePath: str) -> Optional[int]: + sql = insert(avatar).values( + version = version, + avatarAccessoryId = avatarAccessoryId, + name = name, + category = category, + iconPath = iconPath, + texturePath = texturePath, + ) + + conflict = sql.on_duplicate_key_update( + name = name, + category = category, + iconPath = iconPath, + texturePath = texturePath, + ) + + result = self.execute(conflict) + if result is None: return None + return result.lastrowid + diff --git a/titles/chuni/star.py b/titles/chuni/star.py new file mode 100644 index 0000000..03408dc --- /dev/null +++ b/titles/chuni/star.py @@ -0,0 +1,16 @@ +from typing import Dict + +from core.config import CoreConfig +from titles.chuni.base import ChuniBase +from titles.chuni.const import ChuniConstants +from titles.chuni.config import ChuniConfig + +class ChuniStar(ChuniBase): + def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None: + super().__init__(core_cfg, game_cfg) + self.version = ChuniConstants.VER_CHUNITHM_STAR + + def handle_get_game_setting_api_request(self, data: Dict) -> Dict: + ret = super().handle_get_game_setting_api_request(data) + ret["gameSetting"]["dataVersion"] = "1.20.00" + return ret \ No newline at end of file diff --git a/titles/chuni/starplus.py b/titles/chuni/starplus.py new file mode 100644 index 0000000..95000ef --- /dev/null +++ b/titles/chuni/starplus.py @@ -0,0 +1,16 @@ +from typing import Dict + +from core.config import CoreConfig +from titles.chuni.base import ChuniBase +from titles.chuni.const import ChuniConstants +from titles.chuni.config import ChuniConfig + +class ChuniStarPlus(ChuniBase): + def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None: + super().__init__(core_cfg, game_cfg) + self.version = ChuniConstants.VER_CHUNITHM_STAR_PLUS + + def handle_get_game_setting_api_request(self, data: Dict) -> Dict: + ret = super().handle_get_game_setting_api_request(data) + ret["gameSetting"]["dataVersion"] = "1.25.00" + return ret \ No newline at end of file diff --git a/titles/cxb/__init__.py b/titles/cxb/__init__.py new file mode 100644 index 0000000..d57dde0 --- /dev/null +++ b/titles/cxb/__init__.py @@ -0,0 +1,21 @@ +from titles.cxb.index import CxbServlet +from titles.cxb.const import CxbConstants +from titles.cxb.database import CxbData +from titles.cxb.read import CxbReader + +index = CxbServlet +database = CxbData +reader = CxbReader + +use_default_title = False +include_protocol = True +title_secure = True +game_codes = [CxbConstants.GAME_CODE] +trailing_slash = True +use_default_host = False + +include_port = True +uri = "http://$h:$p/" # If you care about the allnet response you're probably running with no SSL +host = "" + +current_schema_version = 1 \ No newline at end of file diff --git a/titles/cxb/base.py b/titles/cxb/base.py new file mode 100644 index 0000000..7b021bb --- /dev/null +++ b/titles/cxb/base.py @@ -0,0 +1,426 @@ +import logging +import json +from decimal import Decimal +from base64 import b64encode +from typing import Any, Dict +from hashlib import md5 +from datetime import datetime + +from core.config import CoreConfig +from titles.cxb.config import CxbConfig +from titles.cxb.const import CxbConstants +from titles.cxb.database import CxbData + +class CxbBase(): + def __init__(self, cfg: CoreConfig, game_cfg: CxbConfig) -> None: + self.config = cfg # Config file + self.game_config = game_cfg + self.data = CxbData(cfg) # Database + self.game = CxbConstants.GAME_CODE + self.logger = logging.getLogger("cxb") + self.version = CxbConstants.VER_CROSSBEATS_REV + + def handle_action_rpreq_request(self, data: Dict) -> Dict: + return({}) + + def handle_action_hitreq_request(self, data: Dict) -> Dict: + return({"data":[]}) + + def handle_auth_usercheck_request(self, data: Dict) -> Dict: + profile = self.data.profile.get_profile_index(0, data["usercheck"]["authid"], self.version) + if profile is not None: + self.logger.info(f"User {data['usercheck']['authid']} has CXB profile") + return({"exist": "true", "logout": "true"}) + + self.logger.info(f"No profile for aime id {data['usercheck']['authid']}") + return({"exist": "false", "logout": "true"}) + + def handle_auth_entry_request(self, data: Dict) -> Dict: + self.logger.info(f"New profile for {data['entry']['authid']}") + return({"token": data["entry"]["authid"], "uid": data["entry"]["authid"]}) + + def handle_auth_login_request(self, data: Dict) -> Dict: + profile = self.data.profile.get_profile_index(0, data["login"]["authid"], self.version) + + if profile is not None: + self.logger.info(f"Login user {data['login']['authid']}") + return({"token": data["login"]["authid"], "uid": data["login"]["authid"]}) + + self.logger.warn(f"User {data['login']['authid']} does not have a profile") + return({}) + + def handle_action_loadrange_request(self, data: Dict) -> Dict: + range_start = data['loadrange']['range'][0] + range_end = data['loadrange']['range'][1] + uid = data['loadrange']['uid'] + + self.logger.info(f"Load data for {uid}") + profile = self.data.profile.get_profile(uid, self.version) + songs = self.data.score.get_best_scores(uid) + + data1 = [] + index = [] + versionindex = [] + + for profile_index in profile: + profile_data = profile_index["data"] + + if int(range_start) == 800000: + return({"index":range_start, "data":[], "version":10400}) + + if not ( int(range_start) <= int(profile_index[3]) <= int(range_end) ): + continue + #Prevent loading of the coupons within the profile to use the force unlock instead + elif 500 <= int(profile_index[3]) <= 510: + continue + #Prevent loading of songs saved in the profile + elif 100000 <= int(profile_index[3]) <= 110000: + continue + #Prevent loading of the shop list / unlocked titles & icons saved in the profile + elif 200000 <= int(profile_index[3]) <= 210000: + continue + #Prevent loading of stories in the profile + elif 900000 <= int(profile_index[3]) <= 900200: + continue + else: + index.append(profile_index[3]) + data1.append(b64encode(bytes(json.dumps(profile_data, separators=(',', ':')), 'utf-8')).decode('utf-8')) + + ''' + 100000 = Songs + 200000 = Shop + 300000 = Courses + 400000 = Events + 500000 = Challenges + 600000 = Bonuses + 700000 = rcLog + 800000 = Partners + 900000 = Stories + ''' + + # Coupons + for i in range(500,510): + index.append(str(i)) + couponid = int(i) - 500 + dataValue = [{ + "couponId":str(couponid), + "couponNum":"1", + "couponLog":[], + }] + data1.append(b64encode(bytes(json.dumps(dataValue[0], separators=(',', ':')), 'utf-8')).decode('utf-8')) + + + # ShopList_Title + for i in range(200000,201451): + index.append(str(i)) + shopid = int(i) - 200000 + dataValue = [{ + "shopId":shopid, + "shopState":"2", + "isDisable":"t", + "isDeleted":"f", + "isSpecialFlag":"f" + }] + data1.append(b64encode(bytes(json.dumps(dataValue[0], separators=(',', ':')), 'utf-8')).decode('utf-8')) + + #ShopList_Icon + for i in range(202000,202264): + index.append(str(i)) + shopid = int(i) - 200000 + dataValue = [{ + "shopId":shopid, + "shopState":"2", + "isDisable":"t", + "isDeleted":"f", + "isSpecialFlag":"f" + }] + data1.append(b64encode(bytes(json.dumps(dataValue[0], separators=(',', ':')), 'utf-8')).decode('utf-8')) + + #Stories + for i in range(900000,900003): + index.append(str(i)) + storyid = int(i) - 900000 + dataValue = [{ + "storyId":storyid, + "unlockState1":["t"] * 10, + "unlockState2":["t"] * 10, + "unlockState3":["t"] * 10, + "unlockState4":["t"] * 10, + "unlockState5":["t"] * 10, + "unlockState6":["t"] * 10, + "unlockState7":["t"] * 10, + "unlockState8":["t"] * 10, + "unlockState9":["t"] * 10, + "unlockState10":["t"] * 10, + "unlockState11":["t"] * 10, + "unlockState12":["t"] * 10, + "unlockState13":["t"] * 10, + "unlockState14":["t"] * 10, + "unlockState15":["t"] * 10, + "unlockState16":["t"] * 10 + }] + data1.append(b64encode(bytes(json.dumps(dataValue[0], separators=(',', ':')), 'utf-8')).decode('utf-8')) + + for song in songs: + song_data = song["data"] + songCode = [] + + songCode.append({ + "mcode": song_data['mcode'], + "musicState": song_data['musicState'], + "playCount": song_data['playCount'], + "totalScore": song_data['totalScore'], + "highScore": song_data['highScore'], + "everHighScore": song_data['everHighScore'] if 'everHighScore' in song_data else ["0","0","0","0","0"], + "clearRate": song_data['clearRate'], + "rankPoint": song_data['rankPoint'], + "normalCR": song_data['normalCR'] if 'normalCR' in song_data else ["0","0","0","0","0"], + "survivalCR": song_data['survivalCR'] if 'survivalCR' in song_data else ["0","0","0","0","0"], + "ultimateCR": song_data['ultimateCR'] if 'ultimateCR' in song_data else ["0","0","0","0","0"], + "nohopeCR": song_data['nohopeCR'] if 'nohopeCR' in song_data else ["0","0","0","0","0"], + "combo": song_data['combo'], + "coupleUserId": song_data['coupleUserId'], + "difficulty": song_data['difficulty'], + "isFullCombo": song_data['isFullCombo'], + "clearGaugeType": song_data['clearGaugeType'], + "fieldType": song_data['fieldType'], + "gameType": song_data['gameType'], + "grade": song_data['grade'], + "unlockState": song_data['unlockState'], + "extraState": song_data['extraState'] + }) + index.append(song_data['index']) + data1.append(b64encode(bytes(json.dumps(songCode[0], separators=(',', ':')), 'utf-8')).decode('utf-8')) + + for v in index: + try: + v_profile = self.data.profile.get_profile_index(0, uid, self.version) + v_profile_data = v_profile["data"] + versionindex.append(int(v_profile_data["appVersion"])) + except: + versionindex.append('10400') + + return({"index":index, "data":data1, "version":versionindex}) + + def handle_action_saveindex_request(self, data: Dict) -> Dict: + save_data = data['saveindex'] + + try: + #REV Omnimix Version Fetcher + gameversion = data['saveindex']['data'][0][2] + self.logger.warning(f"Game Version is {gameversion}") + except: + pass + + if "10205" in gameversion: + self.logger.info(f"Saving CrossBeats REV profile for {data['saveindex']['uid']}") + #Alright.... time to bring the jank code + + for value in data['saveindex']['data']: + + if 'playedUserId' in value[1]: + self.data.profile.put_profile(data['saveindex']['uid'], self.version, value[0], value[1]) + if 'mcode' not in value[1]: + self.data.profile.put_profile(data['saveindex']['uid'], self.version, value[0], value[1]) + if 'shopId' in value: + continue + if 'mcode' in value[1] and 'musicState' in value[1]: + song_json = json.loads(value[1]) + + songCode = [] + songCode.append({ + "mcode": song_json['mcode'], + "musicState": song_json['musicState'], + "playCount": song_json['playCount'], + "totalScore": song_json['totalScore'], + "highScore": song_json['highScore'], + "clearRate": song_json['clearRate'], + "rankPoint": song_json['rankPoint'], + "combo": song_json['combo'], + "coupleUserId": song_json['coupleUserId'], + "difficulty": song_json['difficulty'], + "isFullCombo": song_json['isFullCombo'], + "clearGaugeType": song_json['clearGaugeType'], + "fieldType": song_json['fieldType'], + "gameType": song_json['gameType'], + "grade": song_json['grade'], + "unlockState": song_json['unlockState'], + "extraState": song_json['extraState'], + "index": value[0] + }) + self.data.score.put_best_score(data['saveindex']['uid'], song_json['mcode'], self.version, value[0], songCode[0]) + return({}) + else: + self.logger.info(f"Saving CrossBeats REV Sunrise profile for {data['saveindex']['uid']}") + + #Sunrise + try: + profileIndex = save_data['index'].index('0') + except: + return({"data":""}) #Maybe + + profile = json.loads(save_data["data"][profileIndex]) + aimeId = profile["aimeId"] + i = 0 + + for index, value in enumerate(data["saveindex"]["data"]): + if int(data["saveindex"]["index"][index]) == 101: + self.data.profile.put_profile(aimeId, self.version, data["saveindex"]["index"][index], value) + if int(data["saveindex"]["index"][index]) >= 700000 and int(data["saveindex"]["index"][index])<= 701000: + self.data.profile.put_profile(aimeId, self.version, data["saveindex"]["index"][index], value) + if int(data["saveindex"]["index"][index]) >= 500 and int(data["saveindex"]["index"][index]) <= 510: + self.data.profile.put_profile(aimeId, self.version, data["saveindex"]["index"][index], value) + if 'playedUserId' in value: + self.data.profile.put_profile(aimeId, self.version, data["saveindex"]["index"][index], json.loads(value)) + if 'mcode' not in value and "normalCR" not in value: + self.data.profile.put_profile(aimeId, self.version, data["saveindex"]["index"][index], json.loads(value)) + if 'shopId' in value: + continue + + # MusicList Index for the profile + indexSongList = [] + for value in data["saveindex"]["index"]: + if int(value) in range(100000,110000): + indexSongList.append(value) + + for index, value in enumerate(data["saveindex"]["data"]): + if 'mcode' not in value: + continue + if 'playedUserId' in value: + continue + + data1 = json.loads(value) + + songCode = [] + songCode.append({ + "mcode": data1['mcode'], + "musicState": data1['musicState'], + "playCount": data1['playCount'], + "totalScore": data1['totalScore'], + "highScore": data1['highScore'], + "everHighScore": data1['everHighScore'], + "clearRate": data1['clearRate'], + "rankPoint": data1['rankPoint'], + "normalCR": data1['normalCR'], + "survivalCR": data1['survivalCR'], + "ultimateCR": data1['ultimateCR'], + "nohopeCR": data1['nohopeCR'], + "combo": data1['combo'], + "coupleUserId": data1['coupleUserId'], + "difficulty": data1['difficulty'], + "isFullCombo": data1['isFullCombo'], + "clearGaugeType": data1['clearGaugeType'], + "fieldType": data1['fieldType'], + "gameType": data1['gameType'], + "grade": data1['grade'], + "unlockState": data1['unlockState'], + "extraState": data1['extraState'], + "index": indexSongList[i] + }) + + self.data.score.put_best_score(aimeId, data1['mcode'], self.version, indexSongList[i], songCode[0]) + i += 1 + return({}) + + def handle_action_sprankreq_request(self, data: Dict) -> Dict: + uid = data['sprankreq']['uid'] + self.logger.info(f"Get best rankings for {uid}") + p = self.data.score.get_best_rankings(uid) + + rankList: list[Dict[str, Any]] = [] + + for rank in p: + if rank["song_id"] is not None: + rankList.append({ + "sc": [rank["score"],rank["song_id"]], + "rid": rank["rev_id"], + "clear": rank["clear"] + }) + else: + rankList.append({ + "sc": [rank["score"]], + "rid": rank["rev_id"], + "clear": rank["clear"] + }) + + return({ + "uid": data["sprankreq"]["uid"], + "aid": data["sprankreq"]["aid"], + "rank": rankList, + "rankx":[1,1,1] + }) + + def handle_action_getadv_request(self, data: Dict) -> Dict: + return({"data":[{"r":"1","i":"100300","c":"20"}]}) + + def handle_action_getmsg_request(self, data: Dict) -> Dict: + return({"msgs":[]}) + + def handle_auth_logout_request(self, data: Dict) -> Dict: + return({"auth":True}) + + def handle_action_rankreg_request(self, data: Dict) -> Dict: + uid = data['rankreg']['uid'] + self.logger.info(f"Put {len(data['rankreg']['data'])} rankings for {uid}") + + for rid in data['rankreg']['data']: + #REV S2 + if "clear" in rid: + try: + self.data.score.put_ranking(user_id=uid, rev_id=int(rid["rid"]), song_id=int(rid["sc"][1]), score=int(rid["sc"][0]), clear=rid["clear"]) + except: + self.data.score.put_ranking(user_id=uid, rev_id=int(rid["rid"]), song_id=0, score=int(rid["sc"][0]), clear=rid["clear"]) + #REV + else: + try: + self.data.score.put_ranking(user_id=uid, rev_id=int(rid["rid"]), song_id=int(rid["sc"][1]), score=int(rid["sc"][0]), clear=0) + except: + self.data.score.put_ranking(user_id=uid, rev_id=int(rid["rid"]), song_id=0, score=int(rid["sc"][0]), clear=0) + return({}) + + def handle_action_addenergy_request(self, data: Dict) -> Dict: + uid = data['addenergy']['uid'] + self.logger.info(f"Add energy to user {uid}") + profile = self.data.profile.get_profile_index(0, uid, self.version) + data1 = profile["data"] + p = self.data.item.get_energy(uid) + energy = p["energy"] + + if not p: + self.data.item.put_energy(uid, 5) + + return({ + "class": data1["myClass"], + "granted": "5", + "total": "5", + "threshold": "1000" + }) + + array = [] + + newenergy = int(energy) + 5 + self.data.item.put_energy(uid, newenergy) + + if int(energy) <= 995: + array.append({ + "class": data1["myClass"], + "granted": "5", + "total": str(energy), + "threshold": "1000" + }) + else: + array.append({ + "class": data1["myClass"], + "granted": "0", + "total": str(energy), + "threshold": "1000" + }) + return array[0] + + def handle_action_eventreq_request(self, data: Dict) -> Dict: + self.logger.info(data) + return {"eventreq": ""} + + def handle_action_stampreq_request(self, data: Dict) -> Dict: + self.logger.info(data) + return {"stampreq": ""} diff --git a/titles/cxb/config.py b/titles/cxb/config.py new file mode 100644 index 0000000..e83c1f1 --- /dev/null +++ b/titles/cxb/config.py @@ -0,0 +1,41 @@ +from core.config import CoreConfig + +class CxbServerConfig(): + def __init__(self, parent_config: "CxbConfig"): + self.__config = parent_config + + @property + def enable(self) -> bool: + return CoreConfig.get_config_field(self.__config, 'cxb', 'server', 'enable', default=True) + + @property + def loglevel(self) -> int: + return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'cxb', 'server', 'loglevel', default="info")) + + @property + def hostname(self) -> str: + return CoreConfig.get_config_field(self.__config, 'cxb', 'server', 'hostname', default="localhost") + + @property + def ssl_enable(self) -> bool: + return CoreConfig.get_config_field(self.__config, 'cxb', 'server', 'ssl_enable', default=False) + + @property + def port(self) -> int: + return CoreConfig.get_config_field(self.__config, 'cxb', 'server', 'port', default=8082) + + @property + def port_secure(self) -> int: + return CoreConfig.get_config_field(self.__config, 'cxb', 'server', 'port_secure', default=443) + + @property + def ssl_cert(self) -> str: + return CoreConfig.get_config_field(self.__config, 'cxb', 'server', 'ssl_cert', default="cert/title.crt") + + @property + def ssl_key(self) -> str: + return CoreConfig.get_config_field(self.__config, 'cxb', 'server', 'ssl_key', default="cert/title.key") + +class CxbConfig(dict): + def __init__(self) -> None: + self.server = CxbServerConfig(self) diff --git a/titles/cxb/const.py b/titles/cxb/const.py new file mode 100644 index 0000000..338b8a5 --- /dev/null +++ b/titles/cxb/const.py @@ -0,0 +1,15 @@ +class CxbConstants(): + GAME_CODE = "SDCA" + + CONFIG_NAME = "cxb.yaml" + + VER_CROSSBEATS_REV = 0 + VER_CROSSBEATS_REV_SUNRISE_S1 = 1 + VER_CROSSBEATS_REV_SUNRISE_S2 = 2 + VER_CROSSBEATS_REV_SUNRISE_S2_OMNI = 3 + + VERSION_NAMES = ("crossbeats REV.", "crossbeats REV. SUNRISE", "crossbeats REV. SUNRISE S2", "crossbeats REV. SUNRISE S2 Omnimix") + + @classmethod + def game_ver_to_string(cls, ver: int): + return cls.VERSION_NAMES[ver] \ No newline at end of file diff --git a/titles/cxb/database.py b/titles/cxb/database.py new file mode 100644 index 0000000..8fed1dc --- /dev/null +++ b/titles/cxb/database.py @@ -0,0 +1,13 @@ + +from core.data import Data +from core.config import CoreConfig +from titles.cxb.schema import CxbProfileData, CxbScoreData, CxbItemData, CxbStaticData + +class CxbData(Data): + def __init__(self, cfg: CoreConfig) -> None: + super().__init__(cfg) + + self.profile = CxbProfileData(self.config, self.session) + self.score = CxbScoreData(self.config, self.session) + self.item = CxbItemData(self.config, self.session) + self.static = CxbStaticData(self.config, self.session) diff --git a/titles/cxb/index.py b/titles/cxb/index.py new file mode 100644 index 0000000..62dc70d --- /dev/null +++ b/titles/cxb/index.py @@ -0,0 +1,145 @@ +from twisted.web.http import Request +from twisted.web import resource, server +from twisted.internet import reactor, endpoints +import yaml +import json +import re +import inflection +import logging, coloredlogs +from logging.handlers import TimedRotatingFileHandler +from typing import Dict + +from core.config import CoreConfig +from titles.cxb.config import CxbConfig +from titles.cxb.const import CxbConstants +from titles.cxb.rev import CxbRev +from titles.cxb.rss1 import CxbRevSunriseS1 +from titles.cxb.rss2 import CxbRevSunriseS2 + +class CxbServlet(resource.Resource): + def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None: + self.isLeaf = True + self.cfg_dir = cfg_dir + self.core_cfg = core_cfg + self.game_cfg = CxbConfig() + self.game_cfg.update(yaml.safe_load(open(f"{cfg_dir}/cxb.yaml"))) + + self.logger = logging.getLogger("cxb") + log_fmt_str = "[%(asctime)s] CXB | %(levelname)s | %(message)s" + log_fmt = logging.Formatter(log_fmt_str) + fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.core_cfg.server.log_dir, "cxb"), encoding='utf8', + when="d", backupCount=10) + + fileHandler.setFormatter(log_fmt) + + consoleHandler = logging.StreamHandler() + consoleHandler.setFormatter(log_fmt) + + self.logger.addHandler(fileHandler) + self.logger.addHandler(consoleHandler) + + self.logger.setLevel(self.game_cfg.server.loglevel) + coloredlogs.install(level=self.game_cfg.server.loglevel, logger=self.logger, fmt=log_fmt_str) + + self.versions = [ + CxbRev(core_cfg, self.game_cfg), + CxbRevSunriseS1(core_cfg, self.game_cfg), + CxbRevSunriseS2(core_cfg, self.game_cfg), + ] + + def setup(self): + if self.game_cfg.server.enable: + endpoints.serverFromString(reactor, f"tcp:{self.game_cfg.server.port}:interface={self.core_cfg.server.listen_address}")\ + .listen(server.Site(CxbServlet(self.core_cfg, self.cfg_dir))) + + if self.core_cfg.server.is_develop and self.game_cfg.server.ssl_enable: + endpoints.serverFromString(reactor, f"ssl:{self.game_cfg.server.port_secure}"\ + f":interface={self.core_cfg.server.listen_address}:privateKey={self.game_cfg.server.ssl_key}:"\ + f"certKey={self.game_cfg.server.ssl_cert}")\ + .listen(server.Site(CxbServlet(self.core_cfg, self.cfg_dir))) + + self.logger.info(f"Crossbeats title server ready on port {self.game_cfg.server.port} & {self.game_cfg.server.port_secure}") + else: + self.logger.info(f"Crossbeats title server ready on port {self.game_cfg.server.port}") + + + def render_POST(self, request: Request): + version = 0 + internal_ver = 0 + func_to_find = "" + cmd = "" + subcmd = "" + req_url = request.uri.decode() + url_split = req_url.split("/") + req_bytes = request.content.getvalue() + + try: + req_json: Dict = json.loads(req_bytes) + + except Exception as e: + try: + req_json: Dict = json.loads(req_bytes.decode().replace('"', '\\"').replace("'", '"')) + + except Exception as f: + self.logger.warn(f"Error decoding json: {e} / {f} - {req_url} - {req_bytes}") + return b"" + + if req_json == {}: + self.logger.warn(f"Empty json request to {req_url}") + return b"" + + cmd = url_split[len(url_split) - 1] + subcmd = list(req_json.keys())[0] + + if subcmd == "dldate": + if not type(req_json["dldate"]) is dict or "filetype" not in req_json["dldate"]: + self.logger.warn(f"Malformed dldate request: {req_url} {req_json}") + return b"" + + filetype = req_json["dldate"]["filetype"] + filetype_split = filetype.split("/") + version = int(filetype_split[0]) + filetype_inflect_split = inflection.underscore(filetype).split("/") + + match = re.match("^([A-Za-z]*)(\d\d\d\d)$", filetype_split[len(filetype_split) - 1]) + if match: + subcmd = f"{inflection.underscore(match.group(1))}xxxx" + else: + subcmd = f"{filetype_inflect_split[len(filetype_inflect_split) - 1]}" + else: + filetype = subcmd + + func_to_find = f"handle_{cmd}_{subcmd}_request" + + if version <= 10102: + version_string = "Rev" + internal_ver = CxbConstants.VER_CROSSBEATS_REV + + elif version == 10113 or version == 10103: + version_string = "Rev SunriseS1" + internal_ver = CxbConstants.VER_CROSSBEATS_REV_SUNRISE_S1 + + elif version >= 10114 or version == 10104: + version_string = "Rev SunriseS2" + internal_ver = CxbConstants.VER_CROSSBEATS_REV_SUNRISE_S2 + + else: + version_string = "Base" + + self.logger.info(f"{version_string} Request {req_url} -> {filetype}") + self.logger.debug(req_json) + + try: + handler = getattr(self.versions[internal_ver], func_to_find) + resp = handler(req_json) + + except AttributeError as e: + self.logger.warning(f"Unhandled {version_string} request {req_url} - {e}") + resp = {} + + except Exception as e: + self.logger.error(f"Error handling {version_string} method {req_url} - {e}") + raise + + self.logger.debug(f"{version_string} Response {resp}") + return json.dumps(resp, ensure_ascii=False).encode("utf-8") diff --git a/titles/cxb/read.py b/titles/cxb/read.py new file mode 100644 index 0000000..6117f4e --- /dev/null +++ b/titles/cxb/read.py @@ -0,0 +1,62 @@ +from typing import Optional, Dict, List +from os import walk, path +import urllib +import csv + +from read import BaseReader +from core.config import CoreConfig +from titles.cxb.database import CxbData +from titles.cxb.const import CxbConstants + +class CxbReader(BaseReader): + def __init__(self, config: CoreConfig, version: int, bin_arg: Optional[str], opt_arg: Optional[str], extra: Optional[str]) -> None: + super().__init__(config, version, bin_arg, opt_arg, extra) + self.data = CxbData(config) + + try: + self.logger.info(f"Start importer for {CxbConstants.game_ver_to_string(version)}") + except IndexError: + self.logger.error(f"Invalid project cxb version {version}") + exit(1) + + def read(self) -> None: + pull_bin_ram = True + + if not path.exists(f"{self.bin_dir}"): + self.logger.warn(f"Couldn't find csv file in {self.bin_dir}, skipping") + pull_bin_ram = False + + if pull_bin_ram: + self.read_csv(f"{self.bin_dir}") + + def read_csv(self, bin_dir: str) -> None: + self.logger.info(f"Read csv from {bin_dir}") + + try: + fullPath = bin_dir + "/export.csv" + with open(fullPath, encoding="UTF-8") as fp: + reader = csv.DictReader(fp) + for row in reader: + song_id = row["mcode"] + index = row["index"] + title = row["name"] + artist = row["artist"] + genre = row["category"] + + if not "N/A" in row["standard"]: + self.logger.info(f"Added song {song_id} chart 0") + self.data.static.put_music(self.version, song_id, index, 0, title, artist, genre, int(row["standard"].replace("Standard ","").replace("N/A","0"))) + if not "N/A" in row["hard"]: + self.logger.info(f"Added song {song_id} chart 1") + self.data.static.put_music(self.version, song_id, index, 1, title, artist, genre, int(row["hard"].replace("Hard ","").replace("N/A","0"))) + if not "N/A" in row["master"]: + self.logger.info(f"Added song {song_id} chart 2") + self.data.static.put_music(self.version, song_id, index, 2, title, artist, genre, int(row["master"].replace("Master ","").replace("N/A","0"))) + if not "N/A" in row["unlimited"]: + self.logger.info(f"Added song {song_id} chart 3") + self.data.static.put_music(self.version, song_id, index, 3, title, artist, genre, int(row["unlimited"].replace("Unlimited ","").replace("N/A","0"))) + if not "N/A" in row["easy"]: + self.logger.info(f"Added song {song_id} chart 4") + self.data.static.put_music(self.version, song_id, index, 4, title, artist, genre, int(row["easy"].replace("Easy ","").replace("N/A","0"))) + except: + self.logger.warn(f"Couldn't read csv file in {self.bin_dir}, skipping") diff --git a/titles/cxb/rev.py b/titles/cxb/rev.py new file mode 100644 index 0000000..9a24c17 --- /dev/null +++ b/titles/cxb/rev.py @@ -0,0 +1,256 @@ +import json +from decimal import Decimal +from base64 import b64encode +from typing import Any, Dict +from hashlib import md5 +from datetime import datetime + +from core.config import CoreConfig +from core.data import Data, cached +from titles.cxb.config import CxbConfig +from titles.cxb.base import CxbBase +from titles.cxb.const import CxbConstants + +class CxbRev(CxbBase): + def __init__(self, cfg: CoreConfig, game_cfg: CxbConfig) -> None: + super().__init__(cfg, game_cfg) + self.version = CxbConstants.VER_CROSSBEATS_REV + + def handle_data_path_list_request(self, data: Dict) -> Dict: + return { "data": "" } + + def handle_data_putlog_request(self, data: Dict) -> Dict: + if data["putlog"]["type"] == "ResultLog": + score_data = json.loads(data["putlog"]["data"]) + userid = score_data['usid'] + + self.data.score.put_playlog(userid, score_data['mcode'], score_data['difficulty'], score_data["score"], int(Decimal(score_data["clearrate"]) * 100), score_data["flawless"], score_data["super"], score_data["cool"], score_data["fast"], score_data["fast2"], score_data["slow"], score_data["slow2"], score_data["fail"], score_data["combo"]) + return({"data":True}) + return {"data": True } + + @cached(lifetime=86400) + def handle_data_music_list_request(self, data: Dict) -> Dict: + ret_str = "" + with open(r"titles/cxb/rev_data/MusicArchiveList.csv") as music: + lines = music.readlines() + for line in lines: + line_split = line.split(',') + ret_str += f"{line_split[0]},{line_split[1]},{line_split[2]},{line_split[3]},{line_split[4]},{line_split[5]},{line_split[6]},{line_split[7]},{line_split[8]},{line_split[9]},{line_split[10]},{line_split[11]},{line_split[12]},{line_split[13]},{line_split[14]},\r\n" + + return({"data":ret_str}) + + @cached(lifetime=86400) + def handle_data_item_list_icon_request(self, data: Dict) -> Dict: + ret_str = "\r\n#ItemListIcon\r\n" + with open(r"titles/cxb/rev_data/Item/ItemArchiveList_Icon.csv", encoding="utf-8") as item: + lines = item.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + return({"data":ret_str}) + + @cached(lifetime=86400) + def handle_data_item_list_skin_notes_request(self, data: Dict) -> Dict: + ret_str = "\r\n#ItemListSkinNotes\r\n" + with open(r"titles/cxb/rev_data/Item/ItemArchiveList_SkinNotes.csv", encoding="utf-8") as item: + lines = item.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + return({"data":ret_str}) + + @cached(lifetime=86400) + def handle_data_item_list_skin_effect_request(self, data: Dict) -> Dict: + ret_str = "\r\n#ItemListSkinEffect\r\n" + with open(r"titles/cxb/rev_data/Item/ItemArchiveList_SkinEffect.csv", encoding="utf-8") as item: + lines = item.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + return({"data":ret_str}) + + @cached(lifetime=86400) + def handle_data_item_list_skin_bg_request(self, data: Dict) -> Dict: + ret_str = "\r\n#ItemListSkinBg\r\n" + with open(r"titles/cxb/rev_data/Item/ItemArchiveList_SkinBg.csv", encoding="utf-8") as item: + lines = item.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + return({"data":ret_str}) + + @cached(lifetime=86400) + def handle_data_item_list_title_request(self, data: Dict) -> Dict: + ret_str = "\r\n#ItemListTitle\r\n" + with open(r"titles/cxb/rev_data/Item/ItemList_Title.csv", encoding="shift-jis") as item: + lines = item.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + return({"data":ret_str}) + + @cached(lifetime=86400) + def handle_data_shop_list_music_request(self, data: Dict) -> Dict: + ret_str = "\r\n#ShopListMusic\r\n" + with open(r"titles/cxb/rev_data/Shop/ShopList_Music.csv", encoding="shift-jis") as shop: + lines = shop.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + return({"data":ret_str}) + + @cached(lifetime=86400) + def handle_data_shop_list_icon_request(self, data: Dict) -> Dict: + ret_str = "\r\n#ShopListIcon\r\n" + with open(r"titles/cxb/rev_data/Shop/ShopList_Icon.csv", encoding="shift-jis") as shop: + lines = shop.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + return({"data":ret_str}) + + @cached(lifetime=86400) + def handle_data_shop_list_title_request(self, data: Dict) -> Dict: + ret_str = "\r\n#ShopListTitle\r\n" + with open(r"titles/cxb/rev_data/Shop/ShopList_Title.csv", encoding="shift-jis") as shop: + lines = shop.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + return({"data":ret_str}) + + def handle_data_shop_list_skin_hud_request(self, data: Dict) -> Dict: + return({"data":""}) + + def handle_data_shop_list_skin_arrow_request(self, data: Dict) -> Dict: + return({"data":""}) + + def handle_data_shop_list_skin_hit_request(self, data: Dict) -> Dict: + return({"data":""}) + + @cached(lifetime=86400) + def handle_data_shop_list_sale_request(self, data: Dict) -> Dict: + ret_str = "\r\n#ShopListSale\r\n" + with open(r"titles/cxb/rev_data/Shop/ShopList_Sale.csv", encoding="shift-jis") as shop: + lines = shop.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + return({"data":ret_str}) + + def handle_data_extra_stage_list_request(self, data: Dict) -> Dict: + return({"data":""}) + + @cached(lifetime=86400) + def handle_data_exxxxx_request(self, data: Dict) -> Dict: + extra_num = int(data["dldate"]["filetype"][-4:]) + ret_str = "" + with open(fr"titles/cxb/rev_data/Ex000{extra_num}.csv", encoding="shift-jis") as stage: + lines = stage.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + return({"data":ret_str}) + + def handle_data_bonus_list10100_request(self, data: Dict) -> Dict: + return({"data": ""}) + + def handle_data_free_coupon_request(self, data: Dict) -> Dict: + return({"data": ""}) + + @cached(lifetime=86400) + def handle_data_news_list_request(self, data: Dict) -> Dict: + ret_str = "" + with open(r"titles/cxb/rev_data/NewsList.csv", encoding="UTF-8") as news: + lines = news.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + return({"data":ret_str}) + + def handle_data_tips_request(self, data: Dict) -> Dict: + return({"data":""}) + + @cached(lifetime=86400) + def handle_data_license_request(self, data: Dict) -> Dict: + ret_str = "" + with open(r"titles/cxb/rev_data/License_Offline.csv", encoding="UTF-8") as lic: + lines = lic.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + return({"data":ret_str}) + + @cached(lifetime=86400) + def handle_data_course_list_request(self, data: Dict) -> Dict: + ret_str = "" + with open(r"titles/cxb/rev_data/Course/CourseList.csv", encoding="UTF-8") as course: + lines = course.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + return({"data":ret_str}) + + @cached(lifetime=86400) + def handle_data_csxxxx_request(self, data: Dict) -> Dict: + # Removed the CSVs since the format isnt quite right + extra_num = int(data["dldate"]["filetype"][-4:]) + ret_str = "" + with open(fr"titles/cxb/rev_data/Course/Cs000{extra_num}.csv", encoding="shift-jis") as course: + lines = course.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + return({"data":ret_str}) + + @cached(lifetime=86400) + def handle_data_mission_list_request(self, data: Dict) -> Dict: + ret_str = "" + with open(r"titles/cxb/rev_data/MissionList.csv", encoding="shift-jis") as mission: + lines = mission.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + return({"data":ret_str}) + + def handle_data_mission_bonus_request(self, data: Dict) -> Dict: + return({"data": ""}) + + def handle_data_unlimited_mission_request(self, data: Dict) -> Dict: + return({"data": ""}) + + @cached(lifetime=86400) + def handle_data_event_list_request(self, data: Dict) -> Dict: + ret_str = "" + with open(r"titles/cxb/rev_data/Event/EventArchiveList.csv", encoding="shift-jis") as mission: + lines = mission.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + return({"data":ret_str}) + + def handle_data_event_music_list_request(self, data: Dict) -> Dict: + return({"data": ""}) + + def handle_data_event_mission_list_request(self, data: Dict) -> Dict: + return({"data": ""}) + + def handle_data_event_achievement_single_high_score_list_request(self, data: Dict) -> Dict: + return({"data": ""}) + + def handle_data_event_achievement_single_accumulation_request(self, data: Dict) -> Dict: + return({"data": ""}) + + def handle_data_event_ranking_high_score_list_request(self, data: Dict) -> Dict: + return({"data": ""}) + + def handle_data_event_ranking_accumulation_list_request(self, data: Dict) -> Dict: + return({"data": ""}) + + def handle_data_event_ranking_stamp_list_request(self, data: Dict) -> Dict: + return({"data": ""}) + + def handle_data_event_ranking_store_list_request(self, data: Dict) -> Dict: + return({"data": ""}) + + def handle_data_event_ranking_area_list_request(self, data: Dict) -> Dict: + return({"data": ""}) + + @cached(lifetime=86400) + def handle_data_event_stamp_list_request(self, data: Dict) -> Dict: + ret_str = "" + with open(r"titles/cxb/rev_data/Event/EventStampList.csv", encoding="shift-jis") as event: + lines = event.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + return({"data":ret_str}) + + def handle_data_event_stamp_map_list_csxxxx_request(self, data: Dict) -> Dict: + return({"data": "1,2,1,1,2,3,9,5,6,7,8,9,10,\r\n"}) + + def handle_data_server_state_request(self, data: Dict) -> Dict: + return({"data": True}) diff --git a/titles/cxb/rev_data/Course/CourseList.csv b/titles/cxb/rev_data/Course/CourseList.csv new file mode 100644 index 0000000..bf6a3b6 --- /dev/null +++ b/titles/cxb/rev_data/Course/CourseList.csv @@ -0,0 +1,10 @@ +Cs0000,0,10001,1422284400,4096483201,0,-1,-1,100,0,クリアã™ã‚‹ã¨æ®µä½ãŒã€Œè¦‹ç¿’ã„ã€ã«æ˜‡æ ¼ã—ã¾ã™ã€‚,-1, +Cs0001,1,10001,1422284400,4096483201,0,-1,-1,110,0,クリアã™ã‚‹ã¨æ®µä½ãŒã€Œåˆæ®µã€ã«æ˜‡æ ¼ã—ã¾ã™ã€‚,-1, +Cs0002,2,10001,1422284400,4096483201,0,-1,-1,120,0,クリアã™ã‚‹ã¨æ®µä½ãŒã€ŒäºŒæ®µã€ã«æ˜‡æ ¼ã—ã¾ã™ã€‚,-1, +Cs0003,3,10001,1422284400,4096483201,0,-1,-1,130,0,クリアã™ã‚‹ã¨æ®µä½ãŒã€Œä¸‰æ®µã€ã«æ˜‡æ ¼ã—ã¾ã™ã€‚,-1, +Cs0004,4,10001,1422284400,4096483201,0,-1,-1,140,0,クリアã™ã‚‹ã¨æ®µä½ãŒã€Œå››æ®µã€ã«æ˜‡æ ¼ã—ã¾ã™ã€‚,-1, +Cs0005,5,10001,1422284400,4096483201,0,-1,-1,150,0,クリアã™ã‚‹ã¨æ®µä½ãŒã€Œäº”段ã€ã«æ˜‡æ ¼ã—ã¾ã™ã€‚,-1, +Cs0006,6,10001,1422284400,4096483201,0,-1,-1,160,0,クリアã™ã‚‹ã¨æ®µä½ãŒã€Œå…­æ®µã€ã«æ˜‡æ ¼ã—ã¾ã™ã€‚,-1, +Cs0007,7,10001,1422284400,4096483201,0,-1,-1,170,0,クリアã™ã‚‹ã¨æ®µä½ãŒã€Œä¸ƒæ®µã€ã«æ˜‡æ ¼ã—ã¾ã™ã€‚,-1, +Cs0008,8,10001,1422284400,4096483201,0,-1,-1,180,0,クリアã™ã‚‹ã¨æ®µä½ãŒã€Œå…«æ®µã€ã«æ˜‡æ ¼ã—ã¾ã™ã€‚,-1, +Cs0009,9,10001,1422284400,4096483201,0,-1,-1,190,0,クリアã™ã‚‹ã¨æ®µä½ãŒã€Œä¹æ®µã€ã«æ˜‡æ ¼ã—ã¾ã™ã€‚,-1, diff --git a/titles/cxb/rev_data/Course/Cs0000.csv b/titles/cxb/rev_data/Course/Cs0000.csv new file mode 100644 index 0000000..7dfcd49 --- /dev/null +++ b/titles/cxb/rev_data/Course/Cs0000.csv @@ -0,0 +1,4 @@ +1,dazzli,4,1,-,-1,-1,2,-1,1,-1,1, +2,hokoro,0,1,-,-1,-1,-1,-1,-1,-1,-1, +3,sundro,1,-1,-,-1,-1,-1,-1,-1,-1,-1, +4,sundro,3,-1,-,-1,-1,-1,-1,-1,-1,-1, \ No newline at end of file diff --git a/titles/cxb/rev_data/Course/Cs0001.csv b/titles/cxb/rev_data/Course/Cs0001.csv new file mode 100644 index 0000000..7dfcd49 --- /dev/null +++ b/titles/cxb/rev_data/Course/Cs0001.csv @@ -0,0 +1,4 @@ +1,dazzli,4,1,-,-1,-1,2,-1,1,-1,1, +2,hokoro,0,1,-,-1,-1,-1,-1,-1,-1,-1, +3,sundro,1,-1,-,-1,-1,-1,-1,-1,-1,-1, +4,sundro,3,-1,-,-1,-1,-1,-1,-1,-1,-1, \ No newline at end of file diff --git a/titles/cxb/rev_data/Course/Cs0002.csv b/titles/cxb/rev_data/Course/Cs0002.csv new file mode 100644 index 0000000..7dfcd49 --- /dev/null +++ b/titles/cxb/rev_data/Course/Cs0002.csv @@ -0,0 +1,4 @@ +1,dazzli,4,1,-,-1,-1,2,-1,1,-1,1, +2,hokoro,0,1,-,-1,-1,-1,-1,-1,-1,-1, +3,sundro,1,-1,-,-1,-1,-1,-1,-1,-1,-1, +4,sundro,3,-1,-,-1,-1,-1,-1,-1,-1,-1, \ No newline at end of file diff --git a/titles/cxb/rev_data/Course/Cs0003.csv b/titles/cxb/rev_data/Course/Cs0003.csv new file mode 100644 index 0000000..7dfcd49 --- /dev/null +++ b/titles/cxb/rev_data/Course/Cs0003.csv @@ -0,0 +1,4 @@ +1,dazzli,4,1,-,-1,-1,2,-1,1,-1,1, +2,hokoro,0,1,-,-1,-1,-1,-1,-1,-1,-1, +3,sundro,1,-1,-,-1,-1,-1,-1,-1,-1,-1, +4,sundro,3,-1,-,-1,-1,-1,-1,-1,-1,-1, \ No newline at end of file diff --git a/titles/cxb/rev_data/Course/Cs0004.csv b/titles/cxb/rev_data/Course/Cs0004.csv new file mode 100644 index 0000000..7dfcd49 --- /dev/null +++ b/titles/cxb/rev_data/Course/Cs0004.csv @@ -0,0 +1,4 @@ +1,dazzli,4,1,-,-1,-1,2,-1,1,-1,1, +2,hokoro,0,1,-,-1,-1,-1,-1,-1,-1,-1, +3,sundro,1,-1,-,-1,-1,-1,-1,-1,-1,-1, +4,sundro,3,-1,-,-1,-1,-1,-1,-1,-1,-1, \ No newline at end of file diff --git a/titles/cxb/rev_data/Course/Cs0005.csv b/titles/cxb/rev_data/Course/Cs0005.csv new file mode 100644 index 0000000..7dfcd49 --- /dev/null +++ b/titles/cxb/rev_data/Course/Cs0005.csv @@ -0,0 +1,4 @@ +1,dazzli,4,1,-,-1,-1,2,-1,1,-1,1, +2,hokoro,0,1,-,-1,-1,-1,-1,-1,-1,-1, +3,sundro,1,-1,-,-1,-1,-1,-1,-1,-1,-1, +4,sundro,3,-1,-,-1,-1,-1,-1,-1,-1,-1, \ No newline at end of file diff --git a/titles/cxb/rev_data/Course/Cs0006.csv b/titles/cxb/rev_data/Course/Cs0006.csv new file mode 100644 index 0000000..7dfcd49 --- /dev/null +++ b/titles/cxb/rev_data/Course/Cs0006.csv @@ -0,0 +1,4 @@ +1,dazzli,4,1,-,-1,-1,2,-1,1,-1,1, +2,hokoro,0,1,-,-1,-1,-1,-1,-1,-1,-1, +3,sundro,1,-1,-,-1,-1,-1,-1,-1,-1,-1, +4,sundro,3,-1,-,-1,-1,-1,-1,-1,-1,-1, \ No newline at end of file diff --git a/titles/cxb/rev_data/Course/Cs0007.csv b/titles/cxb/rev_data/Course/Cs0007.csv new file mode 100644 index 0000000..7dfcd49 --- /dev/null +++ b/titles/cxb/rev_data/Course/Cs0007.csv @@ -0,0 +1,4 @@ +1,dazzli,4,1,-,-1,-1,2,-1,1,-1,1, +2,hokoro,0,1,-,-1,-1,-1,-1,-1,-1,-1, +3,sundro,1,-1,-,-1,-1,-1,-1,-1,-1,-1, +4,sundro,3,-1,-,-1,-1,-1,-1,-1,-1,-1, \ No newline at end of file diff --git a/titles/cxb/rev_data/Course/Cs0008.csv b/titles/cxb/rev_data/Course/Cs0008.csv new file mode 100644 index 0000000..7dfcd49 --- /dev/null +++ b/titles/cxb/rev_data/Course/Cs0008.csv @@ -0,0 +1,4 @@ +1,dazzli,4,1,-,-1,-1,2,-1,1,-1,1, +2,hokoro,0,1,-,-1,-1,-1,-1,-1,-1,-1, +3,sundro,1,-1,-,-1,-1,-1,-1,-1,-1,-1, +4,sundro,3,-1,-,-1,-1,-1,-1,-1,-1,-1, \ No newline at end of file diff --git a/titles/cxb/rev_data/Course/Cs0009.csv b/titles/cxb/rev_data/Course/Cs0009.csv new file mode 100644 index 0000000..7dfcd49 --- /dev/null +++ b/titles/cxb/rev_data/Course/Cs0009.csv @@ -0,0 +1,4 @@ +1,dazzli,4,1,-,-1,-1,2,-1,1,-1,1, +2,hokoro,0,1,-,-1,-1,-1,-1,-1,-1,-1, +3,sundro,1,-1,-,-1,-1,-1,-1,-1,-1,-1, +4,sundro,3,-1,-,-1,-1,-1,-1,-1,-1,-1, \ No newline at end of file diff --git a/titles/cxb/rev_data/Event/EventArchiveList.csv b/titles/cxb/rev_data/Event/EventArchiveList.csv new file mode 100644 index 0000000..d15ccb2 --- /dev/null +++ b/titles/cxb/rev_data/Event/EventArchiveList.csv @@ -0,0 +1,39 @@ +Cs1000,0,10000,1443233520,4096483201,1,ev000,1, +Cs1001,0,10000,1443233520,4096483201,1,ev001,1, +Cs1002,0,10000,1443233520,4096483201,1,ev002,1, +Cs1003,0,10000,1443233520,4096483201,1,ev003,1, +Cs1004,0,10000,1443233520,4096483201,1,ev004,1, +Cs1005,0,10000,1443233520,4096483201,1,ev005,1, +Cs1006,0,10000,1443233520,4096483201,1,ev006,1, +Cs1007,0,10000,1443233520,4096483201,1,ev007,1, +Cs1008,0,10000,1443233520,4096483201,1,ev008,1, +Cs1009,0,10000,1443233520,4096483201,1,ev009,1, +Cs1010,0,10000,1443233520,4096483201,1,ev010,1, +Cs1011,0,10000,1443233520,4096483201,1,ev011,1, +Cs1012,0,10000,1443233520,4096483201,1,ev012,1, +Cs1013,0,10000,1443233520,4096483201,1,ev013,1, +Cs1014,0,10000,1443233520,4096483201,1,ev014,1, +Cs1015,0,10000,1443233520,4096483201,1,ev015,1, +Cs1016,0,10000,1443233520,4096483201,1,ev016,1, +Cs1017,0,10000,1443233520,4096483201,1,ev017,1, +Cs1018,0,10000,1443233520,4096483201,1,ev018,1, +Cs1019,0,10000,1443233520,4096483201,1,ev019,1, +Cs1020,0,10000,1443233520,4096483201,1,ev020,1, +Cs1021,0,10000,1443233520,4096483201,1,ev021,1, +Cs1022,0,10000,1443233520,4096483201,1,ev022,1, +Cs1023,0,10000,1443233520,4096483201,1,ev023,1, +Cs1024,0,10000,1443233520,4096483201,1,ev024,1, +Cs1025,0,10000,1443233520,4096483201,1,ev025,1, +Cs1026,0,10000,1443233520,4096483201,1,ev026,1, +Cs1027,0,10000,1443233520,4096483201,1,ev027,1, +Cs1028,0,10000,1443233520,4096483201,1,ev028,1, +Cs1029,0,10000,1443233520,4096483201,1,ev029,1, +Cs1030,0,10000,1443233520,4096483201,1,ev030,1, +Cs1031,0,10000,1443233520,4096483201,1,ev031,1, +Cs1032,0,10000,1443233520,4096483201,1,ev032,1, +Cs1033,0,10000,1443233520,4096483201,1,ev033,1, +Cs1034,0,10000,1443233520,4096483201,1,ev034,1, +Cs1035,0,10000,1443233520,4096483201,1,ev035,1, +Cs1036,0,10000,1443233520,4096483201,1,ev036,1, +Cs1037,0,10000,1443233520,4096483201,1,ev037,1, +Cs1038,0,10000,1443233520,4096483201,1,ev038,1, diff --git a/titles/cxb/rev_data/Event/EventStampList.csv b/titles/cxb/rev_data/Event/EventStampList.csv new file mode 100644 index 0000000..070b792 --- /dev/null +++ b/titles/cxb/rev_data/Event/EventStampList.csv @@ -0,0 +1,39 @@ +Cs1000,1,1,1,1,1,1,1,1,1,1, +Cs1001,1,1,1,1,1,1,1,1,1,1, +Cs1002,1,1,1,1,1,1,1,1,1,1, +Cs1003,1,1,1,1,1,1,1,1,1,1, +Cs1004,1,1,1,1,1,1,1,1,1,1, +Cs1005,1,1,1,1,1,1,1,1,1,1, +Cs1006,1,1,1,1,1,1,1,1,1,1, +Cs1007,1,1,1,1,1,1,1,1,1,1, +Cs1008,1,1,1,1,1,1,1,1,1,1, +Cs1009,1,1,1,1,1,1,1,1,1,1, +Cs1010,1,1,1,1,1,1,1,1,1,1, +Cs1011,1,1,1,1,1,1,1,1,1,1, +Cs1012,1,1,1,1,1,1,1,1,1,1, +Cs1013,1,1,1,1,1,1,1,1,1,1, +Cs1014,1,1,1,1,1,1,1,1,1,1, +Cs1015,1,1,1,1,1,1,1,1,1,1, +Cs1016,1,1,1,1,1,1,1,1,1,1, +Cs1017,1,1,1,1,1,1,1,1,1,1, +Cs1018,1,1,1,1,1,1,1,1,1,1, +Cs1019,1,1,1,1,1,1,1,1,1,1, +Cs1020,1,1,1,1,1,1,1,1,1,1, +Cs1021,1,1,1,1,1,1,1,1,1,1, +Cs1022,1,1,1,1,1,1,1,1,1,1, +Cs1023,1,1,1,1,1,1,1,1,1,1, +Cs1024,1,1,1,1,1,1,1,1,1,1, +Cs1025,1,1,1,1,1,1,1,1,1,1, +Cs1026,1,1,1,1,1,1,1,1,1,1, +Cs1027,1,1,1,1,1,1,1,1,1,1, +Cs1028,1,1,1,1,1,1,1,1,1,1, +Cs1029,1,1,1,1,1,1,1,1,1,1, +Cs1030,1,1,1,1,1,1,1,1,1,1, +Cs1031,1,1,1,1,1,1,1,1,1,1, +Cs1032,1,1,1,1,1,1,1,1,1,1, +Cs1033,1,1,1,1,1,1,1,1,1,1, +Cs1034,1,1,1,1,1,1,1,1,1,1, +Cs1035,1,1,1,1,1,1,1,1,1,1, +Cs1036,1,1,1,1,1,1,1,1,1,1, +Cs1037,1,1,1,1,1,1,1,1,1,1, +Cs1038,1,1,1,1,1,1,1,1,1,1, \ No newline at end of file diff --git a/titles/cxb/rev_data/Ex0000.csv b/titles/cxb/rev_data/Ex0000.csv new file mode 100644 index 0000000..8658453 --- /dev/null +++ b/titles/cxb/rev_data/Ex0000.csv @@ -0,0 +1,10 @@ +StageMax,ClearCount,StageNo.,MCODE,Diff(std),Diff(hrd),Diff(mas),Diff(ulm),Diff(esy),Level(op), Level(val), Grade(op),Grade(val),GT(nml),GT(svl),GT(ult),GT(nhp),GT(esy),HS(op),HS(val),APP,DAP,F-V,F-H,FT(ac),FT(tab),FT(pho),Combo(op),Combo(val),FullCombo,ClearRate(op),ClearRate(val) +2,1,1,-,2,2,2,1,2,-1,-,1,1,-1,-1,-1,-1,-1,1,-2,-1,-1,-1,-1,-1,-1,-1,1,300,-1,-1,-, +2,-,2,-,2,2,2,1,2,-1,-,1,1,-1,-1,-1,-1,-1,1,-2,-1,-1,-1,-1,-1,-1,-1,1,300,-1,-1,-, +3,3,1,-,2,2,1,1,2,-1,-,1,2,-1,-1,-1,-1,-1,1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-,-1,-1,-, +3,-,2,-,2,2,1,1,2,-1,-,1,2,-1,-1,-1,-1,-1,1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-,-1,-1,-, +3,-,3,-,2,2,1,1,2,1,50,1,2,2,2,1,2,2,1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-,-1,-1,-, +4,4,1,dynami:fronti:rebell:fronti2:auflcb:auflcb2,2,2,1,1,2,-1,-,1,-1,-1,-1,-1,-1,-1,1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-,-1,-1,-, +4,-,2,-,2,2,1,1,2,-1,-,1,-1,-1,-1,-1,-1,-1,1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-,-1,-1,-, +4,-,3,-,2,2,1,1,2,-1,-,1,-1,-1,-1,-1,-1,-1,1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-,-1,-1,-, +4,-,4,-,2,2,1,1,2,-1,-,1,-1,-1,-1,-1,-1,-1,1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-,-1,-1,-, diff --git a/titles/cxb/rev_data/Ex0001.csv b/titles/cxb/rev_data/Ex0001.csv new file mode 100644 index 0000000..53ba1e2 --- /dev/null +++ b/titles/cxb/rev_data/Ex0001.csv @@ -0,0 +1,10 @@ +StageMax,ClearCount,StageNo.,MCODE,Diff(std),Diff(hrd),Diff(mas),Diff(ulm),Diff(esy),Level(op), Level(val), Grade(op),Grade(val),GT(nml),GT(svl),GT(ult),GT(nhp),GT(esy),HS(op),HS(val),APP,DAP,F-V,F-H,FT(ac),FT(tab),FT(pho),Combo(op),Combo(val),FullCombo,ClearRate(op),ClearRate(val) +2,1,1,-,2,2,1,1,2,1,50,-1,-,2,2,1,2,2,1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-,1,-1,-, +2,-,2,-,2,2,1,1,2,1,50,-1,-,2,2,1,2,2,1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-,1,-1,-, +3,3,1,-,2,2,1,1,2,1,,-1,-,-1,-1,-1,-1,-1,1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-,-1,-1,-, +3,-,2,-,2,2,1,1,2,1,,-1,-,-1,-1,-1,-1,-1,1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-,-1,-1,-, +3,-,3,-,2,2,1,1,2,1,,-1,-,-1,-1,-1,-1,-1,1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-,-1,-1,-, +4,4,1,dynami:fronti:rebell:fronti2:auflcb:auflcb2,2,2,1,1,2,1,,-1,-,-1,-1,-1,-1,-1,1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-,-1,-1,-, +4,-,2,-,2,2,1,1,2,1,,-1,-,-1,-1,-1,-1,-1,1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-,-1,-1,-, +4,-,3,-,2,2,1,1,2,1,,-1,-,-1,-1,-1,-1,-1,1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-,-1,-1,-, +4,-,4,-,2,2,1,1,2,1,,-1,-,-1,-1,-1,-1,-1,1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-,-1,-1,-, diff --git a/titles/cxb/rev_data/ExtraStageList.csv b/titles/cxb/rev_data/ExtraStageList.csv new file mode 100644 index 0000000..068fd0a --- /dev/null +++ b/titles/cxb/rev_data/ExtraStageList.csv @@ -0,0 +1,2 @@ +No.,Ver.,ESID,Title,StartTime,EndTime,oŒ»‹ÈMCODE,oŒ»RP,oŒ»ƒGƒtƒFƒNƒg,Diff(std),Diff(hrd),Diff(mas),Diff(ulm),Diff(esy),GT(nml),GT(svl),GT(ult),GT(nhp),GT(esy),HS(op),HS(val),APP,DAP,F-V,F-H,FT(ac),FT(tab),FT(pho) +1,1000,Ex0000,MEGALOMAN[i]AoŒ»,1411697520.0288,1443233520.0288,megaro,0,3,2,2,1,2,2,2,2,1,2,2,1,-2,-1,-1,-1,-1,-1,-1,-1, diff --git a/titles/cxb/rev_data/Item/ItemArchiveList_Icon.csv b/titles/cxb/rev_data/Item/ItemArchiveList_Icon.csv new file mode 100644 index 0000000..f61eb72 --- /dev/null +++ b/titles/cxb/rev_data/Item/ItemArchiveList_Icon.csv @@ -0,0 +1,3 @@ +1000,ic0000,IconName0000,, +1001,ic0001,IconName0001,, +1002,ic0002,IconName0002,, diff --git a/titles/cxb/rev_data/Item/ItemArchiveList_SkinBg.csv b/titles/cxb/rev_data/Item/ItemArchiveList_SkinBg.csv new file mode 100644 index 0000000..c93f003 --- /dev/null +++ b/titles/cxb/rev_data/Item/ItemArchiveList_SkinBg.csv @@ -0,0 +1,3 @@ +3000,skb0000,SkinName0000,, +3001,skb0001,SkinName0001,, +3002,skb0002,SkinName0002,, diff --git a/titles/cxb/rev_data/Item/ItemArchiveList_SkinEffect.csv b/titles/cxb/rev_data/Item/ItemArchiveList_SkinEffect.csv new file mode 100644 index 0000000..7c39c75 --- /dev/null +++ b/titles/cxb/rev_data/Item/ItemArchiveList_SkinEffect.csv @@ -0,0 +1,3 @@ +5000,ske0000,SkinName0000,, +5001,ske0001,SkinName0001,, +5002,ske0002,SkinName0002,, diff --git a/titles/cxb/rev_data/Item/ItemArchiveList_SkinNotes.csv b/titles/cxb/rev_data/Item/ItemArchiveList_SkinNotes.csv new file mode 100644 index 0000000..1441690 --- /dev/null +++ b/titles/cxb/rev_data/Item/ItemArchiveList_SkinNotes.csv @@ -0,0 +1,3 @@ +4000,skt0000,SkinName0000,, +4001,skt0001,SkinName0001,, +4002,skt0002,SkinName0002,, diff --git a/titles/cxb/rev_data/Item/ItemList_Title.csv b/titles/cxb/rev_data/Item/ItemList_Title.csv new file mode 100644 index 0000000..0fcc5cf --- /dev/null +++ b/titles/cxb/rev_data/Item/ItemList_Title.csv @@ -0,0 +1,3 @@ +2000,ƒX[ƒp[ƒSƒŠƒ‰,4, +2001,‚ӂ‚¤‚̃SƒŠƒ‰,3, +2002,ƒ‚ƒ“ƒL[,0, \ No newline at end of file diff --git a/titles/cxb/rev_data/License_Offline.csv b/titles/cxb/rev_data/License_Offline.csv new file mode 100644 index 0000000..3122453 --- /dev/null +++ b/titles/cxb/rev_data/License_Offline.csv @@ -0,0 +1,89 @@ +英雄ã®è¨¼ ~ 4Version/カプコンサウンドãƒãƒ¼ãƒ  +© CAPCOM CO., LTD. ALL RIGHTS RESERVED. + +ç¼ç†±ã®åˆƒ ~ ディノãƒãƒ«ãƒ‰ +© CAPCOM CO., LTD. ALL RIGHTS RESERVED. + +å¤ä»£ã®æ¯å¹ã +© CAPCOM CO., LTD. ALL RIGHTS RESERVED. + +Theme of Ryu -SFIV Arrange-/Capcom Sound Team / Hideyuki Fukasawa +©CAPCOM U.S.A., INC. ALL RIGHTS RESERVED. + +Ultra Street Fighter IV/Hideyuki Fukasawa +©CAPCOM U.S.A., INC. ALL RIGHTS RESERVED. + +Theme of Chun-Li -SFIV Arrange-/Capcom Sound Team / Hideyuki Fukasawa +©CAPCOM U.S.A., INC. ALL RIGHTS RESERVED. + +Street Fighter V/Masahiro Aoki +©CAPCOM U.S.A., INC. ALL RIGHTS RESERVED. + +英雄ã®è¨¼ï¼MHF-G 2015 Version +© CAPCOM CO., LTD. ALL RIGHTS RESERVED. + +異ヲ辿リシモノ -対峙-/若林タカツグ +© CAPCOM CO., LTD. ALL RIGHTS RESERVED. + + + + + + + + + + + + + +QLWA(グルーヴコースター 3 リンクフィーãƒãƒ¼ã‚ˆã‚Šï¼‰/t+pazolite +© TAITO CORPORATION 1978,2016 ALL RIGHTS RESERVED. + +グルーヴ・ザ・ãƒãƒ¼ãƒˆï¼ˆã‚°ãƒ«ãƒ¼ãƒ´ã‚³ãƒ¼ã‚¹ã‚¿ãƒ¼ 3 リンクフィーãƒãƒ¼ã‚ˆã‚Šï¼‰/ビートã¾ã‚ŠãŠï¼‹ã‚ã¾ã­ +© TAITO CORPORATION 1978,2016 ALL RIGHTS RESERVED. + +Got hive of Ra(グルーヴコースター 3 リンクフィーãƒãƒ¼ã‚ˆã‚Šï¼‰/E.G.G. +© TAITO CORPORATION 1978,2016 ALL RIGHTS RESERVED. + +LINK LINK FEVER!!!(グルーヴコースター 3 リンクフィーãƒãƒ¼ã‚ˆã‚Šï¼‰/リンカ (CV:豊田èŒçµµï¼‰ +© TAITO CORPORATION 1978,2016 ALL RIGHTS RESERVED. + +SAKURA EXHAUST/RIO HAMAMOTO(BNSI)「太鼓ã®é”人ã€ã‚ˆã‚Š +©BANDAI NAMCO Entertainment Inc. + +カリソメ(グルーヴコースター 3 リンクフィーãƒãƒ¼ã‚ˆã‚Šï¼‰/コンプ(豚乙女) × ichigo(岸田教団 & THE明星ロケッツ) +©上海アリス幻樂団 +© TAITO CORPORATION 1978,2016 ALL RIGHTS RESERVED. + +ãã—ã¦èª°ã‚‚ã„ãªããªã£ãŸï¼ˆã‚°ãƒ«ãƒ¼ãƒ´ã‚³ãƒ¼ã‚¹ã‚¿ãƒ¼ 3 リンクフィーãƒãƒ¼ã‚ˆã‚Šï¼‰/コãƒãƒ¤ã‚·ãƒ¦ã‚¦ãƒ¤(IOSYS) × ã‚ã«ãƒ¼(TaNaBaTa) +©上海アリス幻樂団 +© TAITO CORPORATION 1978,2016 ALL RIGHTS RESERVED. + + + + + + + + + + + + + + + + + +Font Design by Fontworks Inc. +DynaFont is a registered Trademark of DynaComware Taiwan Inc. +Ogg Vorbis is Copyright ©2015, Xiph. Org Foundation +The font used on this product is provided by Hakusyu Fonts co,.Ltd. +キャスティング・ライツクリアランス +æ ªå¼ä¼šç¤¾ãƒ“ーイング +JASRAC許諾第V-1512134å· +e-License許諾番å·GS35000 +VOCALOID and VOCALO are trademarks of Yamaha Corporation. + +OMNiMIX v1.2 diff --git a/titles/cxb/rev_data/MissionList.csv b/titles/cxb/rev_data/MissionList.csv new file mode 100644 index 0000000..9050a43 --- /dev/null +++ b/titles/cxb/rev_data/MissionList.csv @@ -0,0 +1,4 @@ +MissionID,Text,Type,Value_op,Value,Mcode,Difficulty_op,Difficulty,Level_op,Level,Grade_op,Grade,GaugeType_op,GaugeType,HiSpeed_op,HiSpeed,APP,DAP,FlipV,FlipH,Fullcombo,Combo_op,Combo,ClearRate_op,ClearRate,StartTime,StartEnd,District,CoupleId +0,-,-1,-1,-1,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +1,-,-1,-1,-1,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +2,ExtraStage‚ÅMEGALOMAN[i]A‚ðƒNƒŠƒA‚·‚é,0,-1,-1,megaro,-1,-1,-1,-1,1,9,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, diff --git a/titles/cxb/rev_data/MusicArchiveList.csv b/titles/cxb/rev_data/MusicArchiveList.csv new file mode 100644 index 0000000..49cd012 --- /dev/null +++ b/titles/cxb/rev_data/MusicArchiveList.csv @@ -0,0 +1,471 @@ +tutori2,10000,100000,10,10,10,10,10,0,0,0,0,0,0,100000, +tutori3,10000,100000,10,10,10,10,10,0,0,0,0,0,0,100000, +tutori4,10000,100000,10,10,10,10,10,0,0,0,0,0,0,100000, +tutori8,10000,1000000,30,150,350,0,0,1,1,1,0,0,0,100345, +sateli,10000,100000,280,490,770,820,170,1,1,1,1,1,0,100300, +nature,10000,100000,140,240,530,750,50,1,1,1,1,1,3,100301, +purple,10000,100000,220,540,640,730,140,1,1,1,1,1,0,100307, +hearts,10000,100000,180,380,680,770,80,1,1,1,1,1,0,100308, +phasea,10000,100000,160,380,650,750,90,1,1,1,1,1,0,100310, +planet,10000,100000,170,360,490,710,100,1,1,1,1,1,1,100311, +firefo,10000,100000,130,360,570,830,70,1,1,1,1,1,0,100314, +kounen,10000,100000,210,400,660,780,100,1,1,1,1,1,3,100315, +essenc,10000,100000,250,500,700,760,110,1,1,1,1,1,0,100316, +summer,10000,100000,230,570,790,890,130,1,1,1,1,1,0,100317, +tanosi,10000,100000,250,450,700,800,160,1,1,1,1,1,7,100319, +picora,10000,100000,150,380,660,750,80,1,1,1,1,1,1,100320, +devils,10000,100000,270,400,800,0,150,1,1,1,0,1,0,100323, +techno,10000,100000,160,380,510,740,90,1,1,1,1,1,1,100328, +glowww,10000,100000,170,280,420,600,80,1,1,1,1,1,7,100335, +powerr,10000,100000,190,380,690,790,120,1,1,1,1,1,0,100336, +amater,10000,100000,210,480,650,790,130,1,1,1,1,1,0,100340, +advers,10000,100000,150,480,710,830,90,1,1,1,1,1,1,100349, +venera,10000,100000,150,430,680,750,80,1,1,1,1,1,0,100353, +dazaii,10000,100000,210,430,730,770,120,1,1,1,1,1,3,100357, +thesig,10000,100000,210,380,560,730,100,1,1,1,1,1,3,100365, +hosita,10000,100000,160,360,480,650,100,1,1,1,1,1,9999,100344, +bluede,10000,100000,220,410,580,700,120,1,1,1,1,1,5,100372, +emerao,10000,100000,300,530,850,0,190,1,1,1,0,1,1,100373, +megaro,10000,100000,550,800,930,980,0,1,1,1,1,0,0,100129, +angeli,10000,100000,330,560,820,900,220,1,1,1,1,1,0,100330, +moonli,10000,100000,140,430,610,730,80,1,1,1,1,1,9999,100342, +yumemi,10000,100000,120,350,590,690,60,1,1,1,1,1,9999,100369, +pinkym,10000,100000,240,440,740,810,160,1,1,1,1,1,0,100348, +dynami2,10000,100000,180,510,780,800,80,1,1,1,1,1,0,100370, +reseed3,10000,100000,200,550,760,800,100,1,1,1,1,1,0,100306, +toucho,10000,200000,90,280,440,650,50,1,1,1,1,1,1,100002, +ameoto,10000,200000,120,260,470,630,60,1,1,1,1,1,1,100003, +kimito,10000,200000,100,260,490,660,70,1,1,1,1,1,1,100004, +giantk,10000,200000,250,530,710,780,110,1,1,1,1,1,3,100021, +breakd,10000,200000,230,340,570,740,110,1,1,1,1,1,2,100015, +dazzlj,10000,200000,350,600,800,900,160,1,1,1,1,1,1,100028, +ididid,10000,200000,290,460,720,810,160,1,1,1,1,1,1,100093, +sundro,10000,200000,240,470,750,830,140,1,1,1,1,1,3,100042, +auflcb,10000,200000,130,430,810,0,80,1,1,1,0,1,0,100063, +dennou,10000,200000,290,600,760,870,150,1,1,1,1,1,1,100045, +hokoro,10000,200000,290,570,710,810,140,1,1,1,1,1,1,100068, +landin,10000,200000,260,330,490,670,130,1,1,1,1,1,1,100005, +tomorr,10000,100000,150,240,440,620,80,1,1,1,1,1,7,100362, +daybyd,10000,100000,130,260,380,590,60,1,1,1,1,1,7,100363, +syoujo,10000,100000,190,350,530,780,80,1,1,1,1,1,1,100309, +destru,10000,100000,190,410,620,720,100,1,1,1,1,1,0,100352, +gingat,10000,200000,130,290,460,610,50,1,1,1,1,1,0,100041, +daisak,10000,200000,280,360,600,750,120,1,1,1,1,1,0,100066, +paradi,10000,100000,160,280,530,640,100,1,1,1,1,1,3,100376, +pigooo,10000,100000,190,340,590,840,130,1,1,1,1,1,6,100377, +season,10000,300000,150,280,440,650,80,1,1,1,1,1,1,100386, +canonn,10000,300000,170,280,500,830,70,1,1,1,1,1,1,100387, +rhapso,10000,300000,180,340,620,740,60,1,1,1,1,1,1,100388, +turkis,10000,300000,190,390,640,840,110,1,1,1,1,1,1,100389, +biohaz,10000,300000,150,300,510,640,60,1,1,1,1,1,9999,100390, +monhan,10000,300000,100,260,360,540,50,1,1,1,1,1,9999,100391, +gyakut2,10000,300000,130,350,430,560,50,1,1,1,1,1,9999,100392, +street,10000,300000,130,340,470,660,70,1,1,1,1,1,9999,100393, +rockma2,10000,300000,210,340,490,760,140,1,1,1,1,1,9999,100394, +auflcb3,10000,100000,160,360,660,860,60,1,1,1,1,1,9999,100374, +irohaa,10000,100000,190,410,550,760,120,1,1,1,1,1,0,100325, +ibelie,10000,100000,270,470,780,820,140,1,1,1,1,1,0,100326, +monhan2,10000,300000,120,240,430,680,60,1,1,1,1,1,9999,100409, +monhan3,10000,300000,180,280,450,730,80,1,1,1,1,1,9999,100410, +yejiii,10000,100000,220,360,630,790,100,1,1,1,1,1,0,100418, +histor,10000,100000,200,360,560,820,110,1,1,1,1,1,0,100419, +chaset,10000,100000,150,310,580,760,80,1,1,1,1,1,9999,100338, +metall,10000,100000,160,280,570,770,80,1,1,1,1,1,1,100412, +letmeg,10000,100000,180,320,500,720,120,1,1,1,1,1,1,100327, +hontno,10000,200000,120,260,530,660,90,1,1,1,1,1,1,100010, +azitat,10000,200000,240,550,700,830,140,1,1,1,1,1,0,100024, +hellom,10000,100000,180,370,580,720,70,1,1,1,1,1,1,100360, +laught,10000,100000,200,350,510,710,70,1,1,1,1,1,1,100337, +bluede2,10000,100000,160,360,560,810,90,1,1,1,1,1,1,100426, +street2,10000,300000,210,370,550,730,140,1,1,1,1,1,9999,100423, +street3,10000,300000,240,380,570,740,130,1,1,1,1,1,9999,100424, +street4,10000,300000,170,320,510,780,110,1,1,1,1,1,9999,100425, +silbur,10000,100000,200,350,540,750,90,1,1,1,1,1,1,100421, +spicaa,10000,100000,230,360,560,780,100,1,1,1,1,1,1,100422, +tricko,10000,100000,230,340,560,750,110,1,1,1,1,1,0,100438, +thisis,10000,100000,230,370,600,740,120,1,1,1,1,1,4,100435, +rising,10000,100000,230,380,660,850,140,1,1,1,1,1,4,100436, +orbita,10000,100000,200,380,620,740,120,1,1,1,1,1,5,100411, +dddddd,10000,100000,130,330,530,690,90,1,1,1,1,1,5,100433, +pyroma,10000,100000,220,420,700,840,80,1,1,1,1,1,4,100427, +touchn,10000,100000,270,460,680,860,150,1,1,1,1,1,8,100312, +onlyll,10000,100000,210,320,560,690,130,1,1,1,1,1,9999,100359, +upside,10000,100000,180,270,480,670,80,1,1,1,1,1,1,100313, +istanb,10000,100000,410,490,900,980,230,1,1,1,1,1,3,100322, +memori,10000,100000,260,380,650,840,130,1,1,1,1,1,9999,100371, +straye,10000,100000,250,360,610,730,140,1,1,1,1,1,4,100350, +rearhy,10000,100000,170,320,520,690,70,1,1,1,1,1,9999,100358, +hereco,10000,100000,160,340,510,680,110,1,1,1,1,1,4,100432, +thesun,10000,100000,250,400,720,870,130,1,1,1,1,1,4,100441, +sayona,10000,100000,200,340,530,710,100,1,1,1,1,1,9999,100343, +flameu,10000,100000,180,360,570,650,100,1,1,1,1,1,1,100380, +raidon,10000,100000,300,380,580,870,130,1,1,1,1,1,1,100434, +riseup,10000,100000,180,410,670,770,70,1,1,1,1,1,4,100437, +sunglo,10000,100000,180,390,590,720,100,1,1,1,1,1,7,100431, +kinbos,10000,100000,220,380,640,770,120,1,1,1,1,1,3,100439, +densho,10000,100000,280,420,740,900,170,1,1,1,1,1,5,100430, +aiohoo,10000,300000,180,290,420,660,100,1,1,1,1,1,1,100471, +entert,10000,300000,150,330,540,870,90,1,1,1,1,1,6,100472, +takeit,10000,100000,200,380,650,830,120,1,1,1,1,1,4,100457, +harmon,10000,100000,200,360,490,650,120,1,1,1,1,1,7,100449, +avemar,10000,300000,160,310,530,750,80,1,1,1,1,1,5,100428, +mateki,10000,300000,180,350,540,790,100,1,1,1,1,1,5,100429, +lovech,10000,100000,160,300,460,680,80,1,1,1,1,1,7,100445, +akaihe,10000,300000,210,320,500,690,80,1,1,1,1,1,1,100473, +juicys,10000,300000,180,260,450,830,100,1,1,1,1,1,7,100474, +codena,10000,100000,180,350,480,680,90,1,1,1,1,1,1,100468, +groove,10000,300000,220,400,520,730,100,1,1,1,1,1,103,100475, +kansho,10000,100000,130,270,550,740,70,1,1,1,1,1,9999,100450, +overcl2,10000,100000,230,420,580,740,120,1,1,1,1,1,3,100486, +taikoo,10000,300000,130,390,500,750,60,1,1,1,1,1,104,100483, +groove2,10000,300000,150,400,580,850,90,1,1,1,1,1,9999,100480, +overcl,10000,100000,120,350,570,860,80,1,1,1,1,1,4,100487, +notoss,10000,100000,120,420,650,910,80,1,1,1,1,1,4,100466, +machup,10000,100000,170,320,410,710,90,1,1,1,1,1,6,100447, +groove3,10000,300000,180,340,640,850,100,1,1,1,1,1,105,100488, +groove4,10000,300000,220,350,500,750,120,1,1,1,1,1,106,100489, +everyt,10000,100000,220,300,740,0,130,1,1,1,0,1,5,100482, +lespri,10000,100000,250,570,800,0,130,1,1,1,0,1,5,100465, +groove5,10000,300000,240,370,670,0,140,1,1,1,0,1,0,100491, +honeyo,10000,100000,320,630,880,930,240,1,1,1,1,1,6,100490, +groove6,10000,300000,300,640,790,0,220,1,1,1,0,1,3,100494, +sunglo2,10000,100000,210,360,670,810,110,1,1,1,1,1,6,100495, +fourte,10000,100000,240,500,740,800,140,1,1,1,1,1,5,100498, +monhan4,10000,300000,120,400,510,620,50,1,1,1,1,1,9999,100496, +monhan5,10000,300000,120,350,420,650,100,1,1,1,1,1,9999,100497, +darkpa,10000,100000,260,390,700,840,160,1,1,1,1,1,3,100504, +hervor,10000,100000,280,390,730,810,180,1,1,1,1,1,5,100505, +cirnon,10000,300000,240,400,600,790,170,1,1,1,1,1,9999,100499, +marisa,10000,300000,250,410,620,850,180,1,1,1,1,1,9999,100500, +yakini,10000,300000,250,340,580,820,130,1,1,1,1,1,9999,100501, +justic,10000,300000,190,360,570,830,140,1,1,1,1,1,1,100502, +sintyo,10000,300000,250,460,700,830,160,1,1,1,1,1,6,100503, +ascand,10000,100000,320,540,800,900,180,1,1,1,1,1,0,100347, +blackl,10000,100000,190,410,730,840,120,1,1,1,1,1,3,100506, +childr,10000,200000,240,390,560,620,140,1,1,1,1,1,0,100043, +tsukai,10000,200000,190,440,720,760,130,1,1,1,1,1,1,100044, +rideon,10000,200000,290,410,600,800,160,1,1,1,1,1,1,100067, +minest,10000,100000,210,390,620,760,130,1,1,1,1,1,1,100507, +ordine,10000,100000,250,430,730,820,190,1,1,1,1,1,3,100508, +dreamw,10000,100000,260,370,620,750,160,1,1,1,1,1,0,100509, +minerv,10000,100000,320,610,900,0,250,1,1,1,0,1,4,100510, +wannab,10000,200000,90,230,400,650,50,1,1,1,1,1,3,100001, +sekain,10000,100000,260,390,690,780,160,1,1,1,1,1,1,100511, +farawa,10000,100000,230,360,600,760,180,1,1,1,1,1,7,100512, +crissc,10000,200000,370,630,860,910,170,1,1,1,1,1,4,100100, +speedy,10000,100000,220,550,770,0,110,1,1,1,0,1,8,100324, +xxxrev,10000,100000,210,340,560,730,150,1,1,1,1,1,0,100513, +higame,10000,200000,200,300,580,710,130,1,1,1,1,1,3,100016, +theepi,10000,200000,190,400,610,750,140,1,1,1,1,1,3,100022, +anomie,10000,200000,220,380,610,770,150,1,1,1,1,1,0,100023, +crocus,10000,100000,260,370,600,720,150,1,1,1,1,1,7,100524, +lavien,10000,100000,270,410,710,800,180,1,1,1,1,1,5,100546, +megaro2,10000,100000,0,0,990,1000,0,0,0,1,1,0,4,100361, +chipnn,10000,100000,270,340,610,790,160,1,1,1,1,1,6,100541, +yiyoyi,10000,200000,140,330,560,700,70,1,1,1,1,1,3,100007, +binary,10000,200000,170,350,640,890,140,1,1,1,1,1,1,100014, +makaim,10000,200000,300,500,770,0,230,1,1,1,0,1,3,100054, +gyakut,10000,200000,150,210,460,640,60,1,1,1,1,1,1,100055, +basara,10000,200000,190,370,640,730,140,1,1,1,1,1,0,100056, +daybre,10000,300000,160,320,530,720,90,1,1,1,1,1,0,100514, +umiyur,10000,300000,140,280,460,640,80,1,1,1,1,1,1,100515, +chalur,10000,300000,180,400,600,720,140,1,1,1,1,1,9999,100516, +melanc,10000,300000,150,300,500,630,100,1,1,1,1,1,7,100517, +konofu,10000,300000,230,350,620,810,110,1,1,1,1,1,1,100518, +bladem,10000,100000,280,380,630,750,170,1,1,1,1,1,4,100526, +southw,10000,100000,180,270,570,680,120,1,1,1,1,1,7,100536, +ryuuse,10000,100000,210,320,590,0,130,1,1,1,0,1,1,100537, +redhea,10000,300000,270,390,590,720,100,1,1,1,1,1,0,100519, +warnin,10000,300000,250,360,610,740,120,1,1,1,1,1,9999,100520, +topsec,10000,300000,240,340,510,640,130,1,1,1,1,1,9999,100521, +dddoll,10000,300000,260,380,550,630,140,1,1,1,1,1,9999,100522, +tracee,10000,300000,190,310,490,650,90,1,1,1,1,1,0,100548, +drivin,10000,200000,230,400,660,760,80,1,1,1,1,1,7,100111, +genzit,10000,200000,180,460,730,820,120,1,1,1,1,1,0,100118, +aerial,10000,200000,110,280,560,710,50,1,1,1,1,1,1,100039, +einher,10000,100000,290,400,740,800,160,1,1,1,1,1,4,100532, +ariell,10000,100000,190,320,640,730,150,1,1,1,1,1,7,100540, +firstl,10000,100000,250,360,650,770,170,1,1,1,1,1,1,100542, +heartl,10000,100000,230,300,640,0,110,1,1,1,0,1,1,100550, +erasee,10000,100000,220,350,580,680,120,1,1,1,1,1,0,100551, +regene,10000,100000,200,300,560,700,130,1,1,1,1,1,0,100530, +allelu,10000,100000,280,350,640,750,160,1,1,1,1,1,9999,100549, +lighto,10000,100000,250,330,600,740,120,1,1,1,1,1,1,100543, +termin,10000,100000,240,340,630,790,130,1,1,1,1,1,7,100552, +ryuuse2,10000,100000,200,360,620,750,130,1,1,1,1,1,1,100556, +prizmm,10000,100000,210,300,540,0,120,1,1,1,0,1,1,100547, +samalv,10000,200000,190,390,580,770,130,1,1,1,1,1,6,100098, +palpit,10000,100000,290,550,840,920,180,1,1,1,1,1,8,100544, +gainen,10000,100000,260,370,630,0,150,1,1,1,0,1,9999,100558, +moonsh,10000,100000,230,360,620,0,100,1,1,1,0,1,3,100525, +moonki,10000,100000,250,390,640,0,130,1,1,1,0,1,1,100559, +moonri,10000,200000,210,380,580,850,140,1,1,1,1,1,0,100560, +goaway,10000,100000,230,450,590,700,100,1,1,1,1,1,0,100561, +itback,10000,100000,230,380,710,0,120,1,1,1,0,1,3,100567, +redhhh,10000,100000,240,390,770,0,130,1,1,1,0,1,4,100569, +actual,10000,100000,250,380,800,0,140,1,1,1,0,1,0,100568, +zonzon,10000,200000,160,330,630,670,50,1,1,1,1,1,1,100367, +memorm,10000,100000,260,370,730,0,150,1,1,1,0,1,0,100565, +kokoro,10000,100000,200,430,650,690,120,1,1,1,1,1,1,100554, +poweri,10000,100000,260,490,750,910,130,1,1,1,1,1,4,100563, +nisenn,10000,100000,0,0,760,0,0,0,0,1,0,0,8,100555, +yukiya,10000,200000,190,400,610,0,110,1,1,1,0,1,3,100096, +zankyo,10000,200000,180,380,570,740,100,1,1,1,1,1,5,100124, +overlp,10000,200000,170,300,510,0,90,1,1,1,0,1,7,100119, +fracta,10000,100000,310,520,830,0,190,1,1,1,0,1,3,100529, +cantst,10000,100000,230,420,650,0,110,1,1,1,0,1,0,100455, +primaa,10000,100000,180,350,540,750,120,1,1,1,1,1,0,100527, +cyberg,10000,100000,230,350,600,0,120,1,1,1,0,1,0,100448, +freakw,10000,200000,220,420,650,660,130,1,1,1,1,1,0,100018, +aquali,10000,200000,160,340,580,0,110,1,1,1,0,1,4,100006, +takesc,10000,100000,270,370,690,0,100,1,1,1,0,1,1,100572, +cthugh,10000,100000,250,480,730,0,140,1,1,1,0,1,0,100531, +thetaa,10000,100000,210,340,620,0,110,1,1,1,0,1,1,100571, +nekofu,10000,300000,220,340,570,800,100,1,1,1,1,1,6,100493, +howtru,10000,200000,120,250,530,740,80,1,1,1,1,1,0,100057, +romanc,10000,200000,280,550,780,0,100,1,1,1,0,1,0,100047, +kotobu,10000,200000,320,710,900,0,250,1,1,1,0,1,0,100573, +xmasss,10000,300000,180,380,560,770,80,1,1,1,1,1,101,100417, +galaxy,10000,300000,160,320,430,670,100,1,1,1,1,1,0,100600, +rebell,10000,1000000,490,630,910,0,0,1,1,1,0,0,0,100601, +anothe,10000,1000000,270,370,730,760,0,1,1,1,1,0,0,100602, +addict,10000,1000000,200,340,520,620,0,1,1,1,1,0,0,100603, +dirtyy,10000,1000000,150,280,590,740,0,1,1,1,1,0,0,100604, +levelf,10000,300000,110,280,450,630,50,1,1,1,1,1,0,100605, +omnive,10000,1000000,340,520,830,860,0,1,1,1,1,0,0,100606, +kakuse,10000,1000000,170,550,750,0,0,1,1,1,0,0,0,100607, +unbeli,10000,300000,130,260,380,620,70,1,1,1,1,1,0,100608, +sonzai,10000,1000000,260,400,590,660,0,1,1,1,1,0,0,100609, +okonik,10000,1000000,260,450,670,0,0,1,1,1,0,0,0,100610, +crssho,10000,1000000,350,600,850,0,100,1,1,1,0,1,0,100611, +reanim,10000,1000000,280,440,700,800,0,1,1,1,1,0,0,100612, +kamino,10000,1000000,400,620,780,0,150,1,1,1,0,1,0,100613, +fiveee,10000,300000,180,370,610,710,100,1,1,1,1,1,0,100614, +granda,10000,1000000,210,380,790,0,0,1,1,1,0,0,0,100615, +fronti2,10000,1000000,460,690,890,0,90,1,1,1,0,1,0,100616, +saigon,10000,1000000,190,310,570,0,0,1,1,1,0,0,0,100617, +replay,10000,300000,180,440,630,700,80,1,1,1,1,1,0,100618, +mousou,10000,1000000,160,260,540,0,0,1,1,1,0,0,0,100619, +aheadd,10000,300000,130,250,350,580,70,1,1,1,1,1,0,100620, +musicr1,10000,100000,220,330,580,740,120,1,1,1,1,1,0,100621, +getthe,10000,300000,170,370,490,660,60,1,1,1,1,1,0,100622, +design,10000,1000000,150,390,680,690,0,1,1,1,1,0,0,100623, +garnet,10000,1000000,260,460,700,940,0,1,1,1,1,0,0,100624, +hopesb,10000,300000,100,250,440,610,70,1,1,1,1,1,0,100625, +shooti,10000,300000,150,370,490,690,70,1,1,1,1,1,0,100626, +dangan,10000,1000000,280,580,810,0,0,1,1,1,0,0,0,100627, +impact,10000,1000000,240,600,720,900,200,1,1,1,1,1,0,100628, +lightm,10000,300000,260,330,540,710,110,1,1,1,1,1,0,100629, +miiroo,10000,300000,220,390,580,680,110,1,1,1,1,1,0,100630, +voiceo,10000,1000000,180,340,580,590,0,1,1,1,1,0,0,100631, +cosmol,10000,1000000,360,640,870,0,250,1,1,1,0,1,0,100632, +vividd,10000,300000,160,350,550,650,90,1,1,1,1,1,0,100633, +splash,10000,1000000,260,500,710,0,0,1,1,1,0,0,0,100634, +donuth,10000,300000,220,400,540,800,110,1,1,1,1,1,0,100635, +senbon,10000,300000,200,280,540,740,120,1,1,1,1,1,0,100636, +kmtyju,10000,300000,240,310,570,740,120,1,1,1,1,1,0,100637, +fronti,10000,1000000,480,650,820,0,130,1,1,1,0,1,0,100638, +nueraa,10000,1000000,220,430,750,530,0,1,1,1,1,0,0,100639, +childe,10000,300000,90,240,340,560,40,1,1,1,1,1,0,100640, +dazzli2,10000,1000000,350,600,820,0,190,1,1,1,0,1,0,100641, +perfec,10000,1000000,390,640,780,0,0,1,1,1,0,0,0,100642, +flower,10000,300000,70,200,400,650,60,1,1,1,1,1,0,100643, +frgmnt,10000,1000000,330,630,740,650,100,1,1,1,1,1,0,100644, +headph,10000,1000000,240,320,520,0,0,1,1,1,0,0,0,100645, +crsang,10000,1000000,270,530,670,0,130,1,1,1,0,1,0,100646, +musicr4,10000,100000,190,320,580,0,120,1,1,1,0,1,0,100647, +imaxim,10000,1000000,440,690,900,870,0,1,1,1,1,0,0,100648, +azitat2,10000,1000000,230,520,660,0,80,1,1,1,0,1,0,100649, +dynami,10000,1000000,260,540,680,0,110,1,1,1,0,1,0,100650, +incave,10000,1000000,220,440,760,780,0,1,1,1,1,0,0,100651, +aktuki,10000,1000000,260,580,840,0,100,1,1,1,0,1,0,100652, +kindof,10000,1000000,140,290,480,0,0,1,1,1,0,0,0,100653, +mikaku,10000,1000000,190,310,540,0,0,1,1,1,0,0,0,100654, +strang,10000,1000000,120,280,550,0,0,1,1,1,0,0,0,100655, +hesper,10000,1000000,360,610,920,930,0,1,1,1,1,0,0,100656, +breaka,10000,300000,150,310,450,680,70,1,1,1,1,1,0,100657, +myname,10000,1000000,60,140,300,570,0,1,1,1,1,0,0,100658, +amaiko,10000,1000000,150,370,600,0,0,1,1,1,0,0,0,100659, +reseed2,10000,1000000,220,470,630,0,0,1,1,1,0,0,0,100660, +kingst,10000,1000000,380,630,740,0,120,1,1,1,0,1,0,100661, +ramram,10000,1000000,230,340,670,0,0,1,1,1,0,0,0,100662, +murasa,10000,1000000,280,410,760,0,0,1,1,1,0,0,0,100663, +happyd,10000,1100000,220,410,730,790,180,1,1,1,1,1,0,100664, +izimed,10000,300000,190,390,690,770,90,1,1,1,1,1,0,100665, +wastel,10000,1000000,40,120,230,400,0,1,1,1,1,0,0,100666, +assign,10000,1000000,260,430,610,620,0,1,1,1,1,0,0,100667, +jahaci,10000,1000000,170,290,590,0,0,1,1,1,0,0,0,100668, +hisuii,10000,1000000,220,470,700,0,0,1,1,1,0,0,0,100669, +godkno,10000,300000,100,260,450,640,60,1,1,1,1,1,0,100670, +roadof,10000,300000,150,360,500,750,70,1,1,1,1,1,0,100671, +rokuch,10000,300000,210,350,620,810,110,1,1,1,1,1,0,100672, +valent,10000,300000,270,330,590,770,100,1,1,1,1,1,0,100673, +unfini,10000,300000,160,320,500,710,80,1,1,1,1,1,0,100674, +auflcb2,10000,1000000,220,370,750,0,100,1,1,1,0,1,0,100675, +burnin,10000,1000000,180,280,600,850,150,1,1,1,1,1,0,100676, +sphere,10000,1000000,200,380,730,0,0,1,1,1,0,0,0,100677, +dropou,10000,300000,170,310,460,690,140,1,1,1,1,1,0,100678, +xencou,10000,300000,200,320,520,600,80,1,1,1,1,1,0,100679, +killyk,10000,300000,130,420,630,760,60,1,1,1,1,1,0,100680, +missil,10000,1000000,160,380,590,0,0,1,1,1,0,0,0,100681, +burstt,10000,300000,120,250,460,630,70,1,1,1,1,1,0,100682, +musicr2,10000,100000,220,330,580,0,120,1,1,1,0,1,0,100683, +isingl,10000,1000000,250,440,800,0,120,1,1,1,0,1,0,100684, +lvless,10000,1000000,230,380,600,0,0,1,1,1,0,0,0,100685, +sapphi,10000,1000000,290,440,810,0,0,1,1,1,0,0,0,100686, +musicr3,10000,100000,190,320,580,720,120,1,1,1,1,1,0,100687, +deeout,10000,1000000,180,340,630,810,0,1,1,1,1,0,0,100688, +sugars,10000,300000,170,300,420,660,60,1,1,1,1,1,0,100689, +mercur,10000,1000000,140,350,660,0,0,1,1,1,0,0,0,100690, +zizizi,10000,1000000,300,570,880,960,0,1,1,1,1,0,0,100691, +wegooo,10000,300000,180,340,540,680,100,1,1,1,1,1,0,100692, +alonee,10000,300000,110,210,360,480,50,1,1,1,1,1,0,100693, +nuheat,10000,1000000,290,440,650,850,0,1,1,1,1,0,0,100694, +granro,10000,300000,150,280,430,600,80,1,1,1,1,1,0,100695, +sister,10000,300000,100,270,460,630,70,1,1,1,1,1,0,100696, +lotusl,10000,1000000,200,360,640,0,0,1,1,1,0,0,0,100697, +yukari,10000,1000000,310,500,760,840,0,1,1,1,1,0,0,100698, +flawli,10000,300000,170,300,400,590,80,1,1,1,1,1,0,100699, +nightf,10000,1000000,150,280,460,710,0,1,1,1,1,0,0,100700, +random,10000,100000,0,0,0,0,0,0,0,0,0,0,0,100701, +wiwwtw,10000,1000000,260,380,620,0,0,1,1,1,0,0,0,100702, +inneru,10000,300000,220,360,480,670,90,1,1,1,1,1,0,100703, +taishi,10000,1000000,190,350,580,0,0,1,1,1,0,0,0,100704, +daysss,10000,1000000,380,590,810,810,0,1,1,1,1,0,0,100705, +bokuwa,10000,300000,230,340,550,690,160,1,1,1,1,1,0,100706, +showww,10000,300000,180,350,510,790,150,1,1,1,1,1,0,100707, +nevers,10000,300000,260,320,650,750,150,1,1,1,1,1,0,100708, +bleeze,10000,300000,160,310,470,620,90,1,1,1,1,1,0,100709, +dreami,10000,1000000,140,370,650,0,0,1,1,1,0,0,0,100710, +allune,10000,1000000,140,350,710,0,0,1,1,1,0,0,0,100711, +always,10000,1000000,130,270,490,0,0,1,1,1,0,0,0,100712, +anomie2,10000,1000000,160,430,840,0,0,1,1,1,0,0,0,100713, +aquali2,10000,1000000,220,430,600,810,0,1,1,1,1,0,0,100714, +astaro,10000,1000000,230,400,740,0,0,1,1,1,0,0,0,100715, +bassan,10000,1000000,200,320,660,0,0,1,1,1,0,0,0,100716, +zonzon2,10000,1000000,130,270,680,750,0,1,1,1,1,0,0,100717, +bouled,10000,1000000,190,300,570,0,0,1,1,1,0,0,0,100718, +brandn,10000,1000000,90,390,660,720,0,1,1,1,1,0,0,100719, +bravee,10000,1000000,350,600,820,0,250,1,1,1,0,-1,0,100720, +breakd2,10000,1000000,340,640,740,0,0,1,1,1,0,0,0,100721, +buffet,10000,1000000,380,550,680,0,300,1,1,1,0,-1,0,100722, +buzzke,10000,1000000,180,330,580,770,0,1,1,1,1,0,0,100723, +cashhh,10000,1000000,190,250,640,0,0,1,1,1,0,0,0,100724, +cloudb,10000,1000000,370,660,740,0,250,1,1,1,0,-1,0,100725, +clouds,10000,1000000,130,250,470,0,0,1,1,1,0,0,0,100726, +codepa,10000,1000000,290,550,700,0,150,1,1,1,0,-1,0,100727, +comear,10000,1000000,380,560,830,0,250,1,1,1,0,-1,0,100728, +crysta,10000,1000000,370,560,810,0,300,1,1,1,0,-1,0,100729, +curseo,10000,1000000,220,360,740,0,0,1,1,1,0,0,0,100730, +datami,10000,1000000,180,360,660,0,0,1,1,1,0,0,0,100731, +defaul,10000,1000000,210,330,480,0,0,1,1,1,0,0,0,100732, +design2,10000,1000000,250,430,680,0,0,1,1,1,0,0,0,100733, +diamon,10000,1000000,100,260,330,0,0,1,1,1,0,0,0,100734, +dispel,10000,1000000,280,480,800,0,0,1,1,1,0,0,0,100735, +distan,10000,1000000,200,300,680,0,0,1,1,1,0,0,0,100736, +dokibl,10000,1000000,150,230,670,0,0,1,1,1,0,0,0,100737, +dontwa,10000,1000000,130,340,690,0,0,1,1,1,0,0,0,100738, +drgirl,10000,1000000,190,350,540,730,0,1,1,1,1,0,0,100739, +eterna,10000,1000000,120,210,390,0,0,1,1,1,0,0,0,100740, +everkr,10000,1000000,180,290,410,0,0,1,1,1,0,0,0,100741, +everwh,10000,1000000,200,310,580,0,0,1,1,1,0,0,0,100742, +farthe,10000,1000000,300,560,780,870,0,1,1,1,1,0,0,100743, +filame,10000,1000000,230,380,630,0,0,1,1,1,0,0,0,100744, +flameu2,10000,1000000,170,240,590,0,0,1,1,1,0,0,0,100745, +freeee,10000,1000000,190,390,690,0,0,1,1,1,0,0,0,100746, +funkyb2,10000,1000000,210,340,560,0,0,1,1,1,0,0,0,100747, +granda2,10000,1000000,240,410,730,830,0,1,1,1,1,0,0,100748, +hsphsp,10000,1000000,120,250,690,0,0,1,1,1,0,0,0,100749, +halluc,10000,1000000,400,520,870,0,0,1,1,1,0,0,0,100750, +indigo,10000,1000000,170,330,500,750,0,1,1,1,1,0,0,100751, +inters,10000,1000000,250,420,770,0,0,1,1,1,0,0,0,100752, +incave2,10000,1000000,310,570,880,0,0,1,1,1,0,0,0,100753, +ioniza,10000,1000000,170,340,700,850,0,1,1,1,1,0,0,100754, +guilty,10000,1000000,150,280,500,0,0,1,1,1,0,0,0,100755, +keraun,10000,1000000,250,520,790,0,0,1,1,1,0,0,0,100756, +landin2,10000,1000000,200,340,590,660,0,1,1,1,1,0,0,100757, +videog,10000,1000000,210,370,620,0,0,1,1,1,0,0,0,100758, +loseyo,10000,1000000,200,300,710,0,0,1,1,1,0,0,0,100759, +machin,10000,1000000,120,280,720,0,0,1,1,1,0,0,0,100760, +makeit,10000,1000000,110,240,480,0,0,1,1,1,0,0,0,100761, +daydre,10000,1000000,190,360,800,0,0,1,1,1,0,0,0,100762, +metron,10000,1000000,200,440,710,0,0,1,1,1,0,0,0,100763, +milkyw,10000,1000000,220,310,600,0,0,1,1,1,0,0,0,100764, +nayuta,10000,1000000,170,370,680,0,0,1,1,1,0,0,0,100766, +nightm,10000,1000000,200,490,730,0,0,1,1,1,0,0,0,100767, +otherw,10000,1000000,230,410,760,0,0,1,1,1,0,0,0,100768, +overth,10000,1000000,330,570,820,0,250,1,1,1,0,-1,0,100769, +uuuuuu,10000,1000000,230,370,740,0,0,1,1,1,0,0,0,100770, +rainin,10000,1000000,160,410,690,0,0,1,1,1,0,0,0,100771, +raisey,10000,1000000,230,550,750,0,150,1,1,1,0,-1,0,100772, +resona,10000,1000000,170,320,640,0,0,1,1,1,0,0,0,100773, +reuniv,10000,1000000,140,230,410,0,0,1,1,1,0,0,0,100774, +rhythm,10000,1000000,370,560,780,0,250,1,1,1,0,-1,0,100775, +rushhh,10000,1000000,250,370,750,0,0,1,1,1,0,0,0,100776, +steeee,10000,1000000,300,580,870,0,0,1,1,1,0,0,0,100777, +sangey,10000,1000000,270,470,850,0,0,1,1,1,0,0,0,100778, +senpai,10000,1000000,380,540,770,0,250,1,1,1,0,-1,0,100779, +sestea,10000,1000000,270,470,760,0,0,1,1,1,0,0,0,100780, +silver,10000,1000000,280,400,690,0,0,1,1,1,0,0,0,100781, +sodama,10000,1000000,200,400,650,0,0,1,1,1,0,0,0,100782, +stardu,10000,1000000,190,330,640,0,0,1,1,1,0,0,0,100783, +starti,10000,1000000,170,310,540,700,0,1,1,1,1,0,0,100784, +sunday,10000,1000000,180,290,460,670,0,1,1,1,1,0,0,100785, +sundro2,10000,1000000,300,480,790,820,0,1,1,1,1,0,0,100786, +sunnyd,10000,1000000,230,380,590,0,0,1,1,1,0,0,0,100787, +superl,10000,1000000,150,320,590,0,0,1,1,1,0,0,0,100788, +switch,10000,1000000,160,350,690,0,0,1,1,1,0,0,0,100789, +theepi2,10000,1000000,220,370,650,0,0,1,1,1,0,0,0,100790, +epipha,10000,1000000,150,300,700,0,0,1,1,1,0,0,0,100791, +thekin,10000,1000000,220,520,750,0,0,1,1,1,0,0,0,100792, +timele,10000,1000000,160,330,720,0,0,1,1,1,0,0,0,100793, +tokyoo,10000,1000000,150,330,710,0,0,1,1,1,0,0,0,100794, +toooma,10000,1000000,300,510,770,0,0,1,1,1,0,0,0,100795, +toucho2,10000,1000000,170,320,520,780,0,1,1,1,1,0,0,100796, +tayuta,10000,1000000,260,350,720,0,0,1,1,1,0,0,0,100797, +ultrix,10000,1000000,270,450,760,0,0,1,1,1,0,0,0,100798, +underw,10000,1000000,290,460,690,860,0,1,1,1,1,0,0,100799, +virtua,10000,1000000,150,350,630,0,0,1,1,1,0,0,0,100800, +voiceo2,10000,1000000,140,380,670,0,0,1,1,1,0,0,0,100801, +wannab2,10000,1000000,260,410,690,0,0,1,1,1,0,0,0,100802, +wiwwtw2,10000,1000000,200,430,670,720,0,1,1,1,1,0,0,100803, +wingso,10000,1000000,200,530,710,0,0,1,1,1,0,0,0,100804, +winter,10000,1000000,140,240,410,0,0,1,1,1,0,0,0,100805, +iineee,10000,1000000,210,400,810,0,0,1,1,1,0,0,0,100806, +illumi,10000,1000000,100,250,460,630,0,1,1,1,1,0,0,100807, +yellll,10000,1000000,80,170,520,0,0,1,1,1,0,0,0,100808, +eschat,10000,1000000,360,570,770,0,250,1,1,1,0,-1,0,100809, +counte,10000,1000000,290,340,710,0,0,1,1,1,0,0,0,100810, +gimcho,10000,1000000,180,390,700,0,0,1,1,1,0,0,0,100811, +surviv,10000,1000000,240,400,650,0,0,1,1,1,0,0,0,100812, +turkis3,10000,1000000,60,200,480,0,0,1,1,1,0,0,0,100814, +picora2,10000,1000000,280,530,800,0,0,1,1,1,0,0,0,100815, +fortis,10000,1000000,200,370,530,0,0,1,1,1,0,0,0,100816, +hedban,10000,1000000,160,430,660,0,0,1,1,1,0,0,0,100817, +megitu,10000,1000000,150,300,490,0,0,1,1,1,0,0,0,100818, +rockma,10000,1000000,270,480,730,0,0,1,1,1,0,0,0,100819, +kounen2,10000,1000000,210,430,730,0,0,1,1,1,0,0,0,100820, +saisyu,10000,1000000,180,360,560,0,0,1,1,1,0,0,0,100821, +yuukan,10000,1000000,220,330,780,0,0,1,1,1,0,0,0,100822, +modern,10000,1000000,200,320,560,0,0,1,1,1,0,0,0,100823, +miraie,10000,1000000,210,350,660,0,0,1,1,1,0,0,0,100824, +ranfes,10000,1000000,200,420,650,0,0,1,1,1,0,0,0,100825, +nemure,10000,1000000,150,380,670,760,0,1,1,1,1,0,0,100826, +yuwaku,10000,1000000,150,260,430,0,0,1,1,1,0,0,0,100827, +dontst,10000,1000000,150,320,560,700,0,1,1,1,1,0,0,100828, +mottai,10000,1000000,100,260,360,0,0,1,1,1,0,0,0,100829, +slysly,10000,1000000,100,330,580,0,0,1,1,1,0,0,0,100830, +lookam,10000,1000000,170,340,670,0,0,1,1,1,0,0,0,100831, +feverr,10000,1000000,280,480,680,0,0,1,1,1,0,0,0,100832, +fashio,10000,1000000,80,240,390,0,0,1,1,1,0,0,0,100833, +hagito,10000,1000000,120,260,500,0,0,1,1,1,0,0,0,100834, +invade,10000,1000000,100,280,470,0,0,1,1,1,0,0,0,100835, +ainoch,10000,1000000,170,400,590,0,0,1,1,1,0,0,0,100836, +nakama,10000,1000000,140,320,530,0,0,1,1,1,0,0,0,100837, +ninjar,10000,1000000,80,230,410,650,0,1,1,1,1,0,0,100838, +parall,10000,1000000,140,350,610,0,0,1,1,1,0,0,0,100839, +yukifu,10000,1000000,130,290,510,0,0,1,1,1,0,0,0,100840, +furiso,10000,1000000,120,240,440,740,0,1,1,1,1,0,0,100841, +honeyj,10000,100000,320,630,880,930,240,1,1,1,1,1,6,100842, +emeraj,10000,100000,300,530,850,0,190,1,1,1,0,1,1,100843, +dazzlo,10000,200000,350,600,800,900,160,1,1,1,1,1,1,100844, diff --git a/titles/cxb/rev_data/NewsList.csv b/titles/cxb/rev_data/NewsList.csv new file mode 100644 index 0000000..8facf2f --- /dev/null +++ b/titles/cxb/rev_data/NewsList.csv @@ -0,0 +1,10 @@ +1,1,1601510400,4096483201,1,0,0,0,1,0,news0001,,,1,1, +2,1,1601510400,4096483201,1,0,0,0,1,0,news0002,,,1,1, +3,1,1601510400,4096483201,1,0,0,0,1,0,news0003,,,1,1, +4,1,1601510400,4096483201,1,0,0,0,1,0,news0004,,,1,1, +5,1,1601510400,4096483201,0,0,0,1,1,0,news0005,,,1,1, +6,1,1601510400,4096483201,1,0,0,0,1,0,news0006,,,1,1, +7,1,1601510400,4096483201,1,0,0,0,1,0,news0007,,,1,1, +8,1,1601510400,4096483201,1,0,0,0,1,0,news0008,,,1,1, +9,1,1601510400,4096483201,1,0,0,0,1,0,news0009,,,1,1, +10,1,1601510400,4096483201,1,0,0,0,1,0,news0010,,,1,1, diff --git a/titles/cxb/rev_data/Shop/ShopList_Icon.csv b/titles/cxb/rev_data/Shop/ShopList_Icon.csv new file mode 100644 index 0000000..2673e63 --- /dev/null +++ b/titles/cxb/rev_data/Shop/ShopList_Icon.csv @@ -0,0 +1,4 @@ +shopID.,®——pNo.,Ver.,oŒ»ƒtƒ‰ƒO,oŒ»ƒtƒ‰ƒOŽQÆID,ðŒ,oŒ»ŽžŠÔ,Á–ÅŽžŠÔ,ItemCode,‰¿Ši,•\Ž¦ƒ^ƒCƒv,Text,Type,Value(op),Value,‘ÎÛ‹È,Difficulty(op),Difficulty,Level(op),Level,Grade(Op),Grade,GaugeType(op),GaugeType,HS(op)i,HS,APP,DAP,F-V,F-H,FullCombo,Combo(op),Combo,ClearRate(op),ClearRate,ƒvƒŒƒC“ú,’nˆæ, +1000,1,1.00.00,1,-1,-,1411697520.0288,1443233520.0288,ic0000,10,1,MASTERˆÈã‚Ì2‹ÈS+ˆÈãƒtƒ‹ƒRƒ“ƒ{ƒNƒŠƒA‚·‚éB,1,1,1,-,1,2,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +1001,2,1.00.00,1,-1,-,1411697520.0288,1443233520.0288,ic0001,10,1,Next Frontier (Masterj‚ðƒNƒŠƒA,0,-1,-1,megaro,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +1002,3,1.00.00,1,-1,-,1412103600.0288,1443639598.992,ic0002,10,2,MasterˆÈã‚ð1‹È‚ðS+ˆÈã‚ŃNƒŠƒA‚·‚éB,1,1,1,-,1,2,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,1, diff --git a/titles/cxb/rev_data/Shop/ShopList_Music.csv b/titles/cxb/rev_data/Shop/ShopList_Music.csv new file mode 100644 index 0000000..0e672c2 --- /dev/null +++ b/titles/cxb/rev_data/Shop/ShopList_Music.csv @@ -0,0 +1,13 @@ +shopID.,®——pNo.,Ver.,oŒ»ƒtƒ‰ƒO,oŒ»ƒtƒ‰ƒOŽQÆID,ðŒ,oŒ»ŽžŠÔ,Á–ÅŽžŠÔ,MusicCode,‰¿Ši,•\Ž¦ƒ^ƒCƒv,Text,Type,Value(op),Value,‘ÎÛ‹È,Difficulty(op),Difficulty,Level(op),Level,Grade(Op),Grade,GaugeType(op),GaugeType,HS(op),HS,APP,DAP,F-V,F-H,FullCombo,Combo(op),Combo,ClearRate(op),ClearRate,ƒvƒŒƒC“ú,’nˆæ, +0,1,1.00.00,3,1,-,1411697520.0288,1443233520.0288,megaro,0,0,-,-1,-1,-1,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +1,2,1.00.00,1,-1,-,1411697520.0288,1443233520.0288,nature,10,2,???,-1,-1,-1,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +2,3,1.00.00,2,-1,-,1412103600.0288,1443639598.992,hopesb,30,1,uLanding on the moonv‚ðSTANDARDˆÈã‚ŃNƒŠƒA‚·‚éB,0,-1,-1,landin,1,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +3,4,1.00.00,1,-1,-,1412103600.0288,1443639598.992,flower,10,0,-,-1,-1,-1,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +4,5,1.00.02,1,-1,1&2&3,1412103600.0288,1443639598.992,reseed3,10,0,uHuman NaturevuHopes BrightvuFlowerwallv‚ðƒNƒŠƒA‚·‚éB,-1,-1,-1,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +5,6,1.00.00,1,-1,-,1411697520.0288,1443233520.0288,dennou,20,1,-,0,-1,-1,flower,1,2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,2,-1,-1,-1,-1,,, +6,7,1.00.00,2,-1,5&7,1411697520.0288,1443233520.0288,romanc,10,1,-,-1,-1,-1,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +7,8,1.00.00,1,-1,-,1411697520.0288,1443233520.0288,landin,40,1,-,-1,-1,-1,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +8,9,1.00.00,1,-1,7,1411697520.0288,1443233520.0288,ididid,50,0,-,-1,-1,-1,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,2,-1,-1,-1,-1,,, +9,10,1.00.00,1,-1,-,1411697520.0288,1443233520.0288,crissc,60,0,MASTERˆÈã‚Ì2‹ÈS+ˆÈãƒtƒ‹ƒRƒ“ƒ{ƒNƒŠƒA‚·‚éB,1,1,2,-,1,2,-1,0,1,1,1,2,-1,-1,-1,-1,-1,-1,1,-1,-1,-1,-1,,, +10,11,1.00.00,1,-1,-,1411697520.0288,1443233520.0288,dazzli,70,1,MASTERˆÈã‚Ì4‹ÈS+ˆÈãƒNƒŠƒA‚·‚éB,1,1,4,-,1,2,-1,0,1,1,1,2,-1,-1,-1,-1,-1,-1,2,-1,-1,-1,-1,,, +11,12,1.00.00,1,-1,-,1411697520.0288,1443233520.0288,izimed,987654,1,MASTERˆÈã‚Ì7‹ÈS+ˆÈãƒNƒŠƒA‚·‚éB,1,1,7,-,1,2,-1,0,1,1,1,2,-1,-1,-1,-1,-1,-1,2,-1,-1,-1,-1,,, diff --git a/titles/cxb/rev_data/Shop/ShopList_Sale.csv b/titles/cxb/rev_data/Shop/ShopList_Sale.csv new file mode 100644 index 0000000..1e71623 --- /dev/null +++ b/titles/cxb/rev_data/Shop/ShopList_Sale.csv @@ -0,0 +1,3 @@ +saleID.,ŠJŽn“ú,I—¹“ú,ShopID,Price, +0,1411696799.9712,1443236400,0,7000, +1,1411783199.9712,1443322800,1,7000, diff --git a/titles/cxb/rev_data/Shop/ShopList_SkinBg.csv b/titles/cxb/rev_data/Shop/ShopList_SkinBg.csv new file mode 100644 index 0000000..44c4843 --- /dev/null +++ b/titles/cxb/rev_data/Shop/ShopList_SkinBg.csv @@ -0,0 +1,4 @@ +shopID.,®——pNo.,Ver.,oŒ»ƒtƒ‰ƒO,oŒ»ƒtƒ‰ƒOŽQÆID,ðŒ,oŒ»ŽžŠÔ,Á–ÅŽžŠÔ,ItemCode,‰¿Ši,•\Ž¦ƒ^ƒCƒv,Text,Type,Value(op),Value,‘ÎÛ‹È,Difficulty(op),Difficulty,Level(op),Level,Grade(Op),Grade,GaugeType(op),GaugeType,HS(op)i,HS,APP,DAP,F-V,F-H,FullCombo,Combo(op),Combo,ClearRate(op),ClearRate,ƒvƒŒƒC“ú,’nˆæ, +3000,1,1.00.00,1,-1,-,1411697520.0288,1443233520.0288,skb0000,10,1,MASTERˆÈã‚Ì2‹ÈS+ˆÈãƒtƒ‹ƒRƒ“ƒ{ƒNƒŠƒA‚·‚éB,1,1,1,-,1,2,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +3001,2,1.00.00,1,-1,-,1411697520.0288,1443233520.0288,skb0001,10,1,Next Frontier (Masterj‚ðƒNƒŠƒA,0,-1,-1,bleeze,1,2,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +3002,3,1.00.00,1,-1,-,1412103600.0288,1443639598.992,skb0002,10,2,MasterˆÈã‚ð1‹È‚ðS+ˆÈã‚ŃNƒŠƒA‚·‚éB,1,1,1,-,1,2,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,1, diff --git a/titles/cxb/rev_data/Shop/ShopList_SkinEffect.csv b/titles/cxb/rev_data/Shop/ShopList_SkinEffect.csv new file mode 100644 index 0000000..cc419a4 --- /dev/null +++ b/titles/cxb/rev_data/Shop/ShopList_SkinEffect.csv @@ -0,0 +1,11 @@ +shopID.,®——pNo.,Ver.,oŒ»ƒtƒ‰ƒO,oŒ»ƒtƒ‰ƒOŽQÆID,ðŒ,oŒ»ŽžŠÔ,Á–ÅŽžŠÔ,ItemCode,‰¿Ši,•\Ž¦ƒ^ƒCƒv,Text,Type,Value(op),Value,‘ÎÛ‹È,Difficulty(op),Difficulty,Level(op),Level,Grade(Op),Grade,GaugeType(op),GaugeType,HS(op)i,HS,APP,DAP,F-V,F-H,FullCombo,Combo(op),Combo,ClearRate(op),ClearRate,ƒvƒŒƒC“ú,’nˆæ, +5000,1,1.00.00,1,-1,-,1411697520.0288,1443233520.0288,ske0000,10,1,MASTERˆÈã‚Ì2‹ÈS+ˆÈãƒtƒ‹ƒRƒ“ƒ{ƒNƒŠƒA‚·‚éB,1,1,1,-,1,2,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +5001,2,1.00.00,1,-1,-,1411697520.0288,1443233520.0288,ske0001,10,1,Next Frontier (Masterj‚ðƒNƒŠƒA,0,-1,-1,megaro,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +5002,3,1.00.00,1,-1,-,1412103600.0288,1443639598.992,ske0002,10,2,MasterˆÈã‚ð1‹È‚ðS+ˆÈã‚ŃNƒŠƒA‚·‚éB,1,1,1,-,1,2,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,1, +5003,4,1.00.00,1,-1,-,1412103600.0288,1443639598.992,ske0003,10,0,MasterˆÈã‚ð1‹È‚ðS+ˆÈã‚ŃNƒŠƒA‚·‚éB,1,1,1,-,1,2,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +5004,5,1.00.00,1,-1,-,1412103600.0288,1443639598.992,ske0004,10,2,2‹ÈƒNƒŠƒA,1,1,2,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +5005,5,1.00.00,1,-1,-,1412103600.0288,1443639598.992,ske0005,10,2,3‹ÈƒNƒŠƒA,1,1,3,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +5006,5,1.00.00,1,-1,-,1412103600.0288,1443639598.992,ske0006,10,2,4‹ÈƒNƒŠƒA,1,1,4,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +5007,5,1.00.00,1,-1,-,1412103600.0288,1443639598.992,ske0007,10,2,5‹ÈƒNƒŠƒA,1,1,5,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +5008,5,1.00.00,1,-1,-,1412103600.0288,1443639598.992,ske0008,10,2,6‹ÈƒNƒŠƒA,1,1,6,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +5009,5,1.00.00,1,-1,-,1412103600.0288,1443639598.992,ske0009,10,2,7‹ÈƒNƒŠƒA,1,1,7,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, diff --git a/titles/cxb/rev_data/Shop/ShopList_SkinNotes.csv b/titles/cxb/rev_data/Shop/ShopList_SkinNotes.csv new file mode 100644 index 0000000..8550a96 --- /dev/null +++ b/titles/cxb/rev_data/Shop/ShopList_SkinNotes.csv @@ -0,0 +1,6 @@ +shopID.,®——pNo.,Ver.,oŒ»ƒtƒ‰ƒO,oŒ»ƒtƒ‰ƒOŽQÆID,ðŒ,oŒ»ŽžŠÔ,Á–ÅŽžŠÔ,ItemCode,‰¿Ši,•\Ž¦ƒ^ƒCƒv,Text,Type,Value(op),Value,‘ÎÛ‹È,Difficulty(op),Difficulty,Level(op),Level,Grade(Op),Grade,GaugeType(op),GaugeType,HS(op)i,HS,APP,DAP,F-V,F-H,FullCombo,Combo(op),Combo,ClearRate(op),ClearRate,ƒvƒŒƒC“ú,’nˆæ, +4000,1,1.00.00,1,-1,-,1411697520.0288,1443233520.0288,skt0000,10,1,MASTERˆÈã‚Ì2‹ÈS+ˆÈãƒtƒ‹ƒRƒ“ƒ{ƒNƒŠƒA‚·‚éB,1,1,1,-,1,2,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +4001,2,1.00.00,1,-1,-,1411697520.0288,1443233520.0288,skt0001,10,1,Next Frontier (Masterj‚ðƒNƒŠƒA,0,-1,-1,megaro,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +4002,3,1.00.00,1,-1,-,1412103600.0288,1443639598.992,skt0002,10,2,MasterˆÈã‚ð1‹È‚ðS+ˆÈã‚ŃNƒŠƒA‚·‚éB,1,1,1,-,1,2,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,1, +4003,4,1.00.00,1,-1,-,1412103600.0288,1443639598.992,skt0003,10,0,MasterˆÈã‚ð1‹È‚ðS+ˆÈã‚ŃNƒŠƒA‚·‚éB,1,1,1,-,1,2,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +4004,5,1.00.00,1,-1,-,1412103600.0288,1443639598.992,skt0004,10,2,aaaaaaaaaaaaaaaaa,1,1,20,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, diff --git a/titles/cxb/rev_data/Shop/ShopList_Title.csv b/titles/cxb/rev_data/Shop/ShopList_Title.csv new file mode 100644 index 0000000..76f2e32 --- /dev/null +++ b/titles/cxb/rev_data/Shop/ShopList_Title.csv @@ -0,0 +1,4 @@ +shopID.,®——pNo.,Ver.,oŒ»ƒtƒ‰ƒO,oŒ»ƒtƒ‰ƒOŽQÆID,ðŒ,oŒ»ŽžŠÔ,Á–ÅŽžŠÔ,ItemCode,‰¿Ši,•\Ž¦ƒ^ƒCƒv,Text,Type,Value(op),Value,‘ÎÛ‹È,Difficulty(op),Difficulty,Level(op),Level,Grade(Op),Grade,GaugeType(op),GaugeType,HS(op)i,HS,APP,DAP,F-V,F-H,FullCombo,Combo(op),Combo,ClearRate(op),ClearRate,ƒvƒŒƒC“ú,’nˆæ, +2000,1,1.00.00,1,-1,-,1411697520.0288,1443233520.0288,,10,1,MASTERˆÈã‚Ì2‹ÈS+ˆÈãƒtƒ‹ƒRƒ“ƒ{ƒNƒŠƒA‚·‚éB,1,1,1,-,1,2,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +2001,2,1.00.00,1,-1,-,1411697520.0288,1443233520.0288,,10,1,Next Frontier (Masterj‚ðƒNƒŠƒA,0,-1,-1,megaro,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +2002,3,1.00.00,1,-1,-,1412103600.0288,1443639598.992,,10,2,MasterˆÈã‚ð1‹È‚ðS+ˆÈã‚ŃNƒŠƒA‚·‚éB,1,1,1,-,1,2,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,1, diff --git a/titles/cxb/rev_data/SkinArchiveList.csv b/titles/cxb/rev_data/SkinArchiveList.csv new file mode 100644 index 0000000..4279ed1 --- /dev/null +++ b/titles/cxb/rev_data/SkinArchiveList.csv @@ -0,0 +1,116 @@ +mu000,01e0caad-eaee-4810-90ae-6f45abe5c266, +ig000,dcac41a7-7816-406e-b750-283e06e77a1b, +bg000,e3dc498b-79bd-4110-a0a9-7ab6538ec6a6, +mu001,c4dc9846-df4b-4c3d-89b7-e19ae9288de1, +ig001,13bc8578-e2bf-45ba-ad10-23831e95f8de, +bg001,19a69ba1-a408-4f0c-a923-1d42e7d9eb0e, +mu999,3e83ee1c-6936-4e21-a10c-5d359749bb83, +ig002,73a5a9d6-c862-4cd0-a36f-26fe73da78de, +ig003,9e1d3817-ef60-4d8d-9beb-c0749b6b51fd, +ig004,90e6f2da-9678-4847-8435-30f238f214ab, +ig005,681c7bb1-8f73-4753-8945-f46572716bff, +th000,6a422181-03e0-4b97-b443-22ad0004e85a, +th001,ad9dfefd-a687-4cca-8e89-6245c94e52c9, +th999,fbf16d3c-1718-4b7d-af3d-68f8b8e18867, +ig006,578d6c5c-10f6-4c0e-a99b-29bab4938ade, +ev999,a02c3fb7-53bc-4380-93da-57988ed3b9d2, +mu002,aef73754-f2e7-47c4-bfb3-eb5366aa2b6d, +ig007,3ac11d24-3dbe-4800-a2ed-6bbc4e093c6d, +bg002,70ae1a4e-1c4d-4f12-a326-c3fac0e87045, +th002,9ee6fd76-4b9c-44c2-90be-a50530f0313f, +mu003,09c4fd8b-111b-4dbb-92c3-94fcfb14a311, +ig008,869bb37b-0abb-4c4f-a56a-03f7ee98ea14, +bg003,d4165292-3140-41e4-bde9-a1a530c462ab, +th003,6053e709-d3c5-4110-9b07-47876bba00cf, +ig009,b8e368c3-085c-467b-9654-90c63cb390f7, +ig010,7e3e2b96-0cf9-4920-9264-6f284019dee7, +bg999,5ba48136-a854-4299-8790-c1c371945eb0, +bg004,25cb7abb-ad50-469a-9d28-440f3ffec9e8, +ig011,7b92ac76-9750-4efe-ad92-e43844d8e9e2, +mu004,abfa94c5-a303-4c7a-bacd-bbbda7d3877e, +ig012,de774207-7a71-42cd-b54e-054b67199f7d, +bg005,2aa16e78-893a-4e34-95ef-c7d205a40ea3, +th004,6589a0cc-0b2d-46b2-a21c-49ad68deeff5, +mu005,f1ce21fc-f512-43e5-af39-001278d6e8ac, +ig013,364a9449-bb7b-47ed-b72b-4f7a5583966b, +bg006,b2212579-69b0-480e-a4ab-685bf5c2599d, +th005,9392bbee-3762-4d19-a382-1bc1e4d38dc5, +ig014,45ecd41a-976f-42cb-bbb7-9c70946f7793, +ig015,a2f8cb15-e579-41d4-958a-42cfb3db08f2, +ig016,fd991b2f-22a2-4b4b-95ec-06ae70fa4cf8, +ig020,b0d366c4-644f-403f-aa0b-87ec781c5a47, +ig030,c3a59d08-0c9b-4577-9e3c-3b828a5dbae4, +mu007,cc4d20dd-0db2-4128-b366-cc466c074edd, +mu008,873550c2-8c69-4d32-9af0-b1193a3297ff, +mu009,966b2c02-f467-4b12-811c-d5df28378b88, +mu010,93b7cf77-5f4e-4060-839c-d07ea1cb9293, +mu011,8fa1af29-61d4-4ed3-bf5d-1755cc63f597, +mu013,e62242b0-22c5-435c-a904-97fc815113b3, +mu014,f5ba080a-be40-4d99-a7b5-65ac370c6625, +mu015,492e20bb-5394-4159-938e-0c626a69fbb8, +mu016,c3db7891-26f1-4070-bd58-fb193c3d6b98, +mu017,c43be79b-90ff-4a3c-90a0-4b01f16175b8, +mu018,3cbc6540-95c5-40ba-a17b-b37044695bbd, +mu012,d4407594-5928-4606-8831-0becd9a82e79, +th007,4db1a0a5-f0a8-4909-83eb-d5a53de174fc, +ig017,52c31a94-ef44-4dbd-a26a-4aa43590c998, +ig018,2ea1ba61-352c-4ff3-8854-ae34ec6a3cb8, +ig019,f79e01a8-f325-409c-8b55-b40fd09f485b, +ig021,0abe9547-fc5d-4677-8469-7da6b0dcc46d, +ig022,b2eba0ca-08f3-412b-a4ff-df301aef59a5, +ig023,2a615618-df3c-4eb5-8f9a-cdceed2f70b2, +ig024,bac5cd51-1ae3-483e-bac0-ddd0aa79a544, +ig025,39241b3b-c3bb-4852-9bc1-b5ad4950788d, +ig026,c5e90844-2e42-4702-95c0-536f1f707b69, +ig027,243e96c0-c3bb-4cbf-8714-39e637a1fc4c, +ig028,c9663a15-3379-4c26-833e-c0cbe75f1725, +ig029,bd4f1868-530f-47ff-8ad9-331f025a30cd, +ig031,a50830c8-85cf-4b90-9f76-369c6786aa10, +ig032,7547c19d-c0e8-4fa2-bb02-a0818c908f1d, +ig033,f8163e70-a91b-4c37-8ff0-c519ac41789d, +ig034,32649798-7da7-4bef-94ae-eac2823dbdca, +ig035,f61efc53-582e-424b-9c6a-27b714414ac7, +ig036,31c83605-88f3-4190-a875-40afca619bc7, +ig037,c7c94234-e593-47cc-9440-3ae6e5700d4e, +ig038,414fed22-596a-4b82-b322-0592b6284c6c, +ig039,6832ca35-7187-4ca9-a677-c5587f3dfc37, +ig040,3e1a72fd-de2a-4de1-b9cb-c6b70e8dd865, +ig042,6b689c36-f388-48e8-93d3-2c73c71b2279, +ig043,f5d69985-5662-40e2-a184-2da3258f77ea, +ig044,8b0605fe-06de-4b2a-9c31-a229b8b5da3f, +mu019,31ad932c-990e-4967-90b6-188f2c656dbb, +bg007,28f01dfc-e73d-4701-a2d1-ac929785ea98, +bg008,ba10a865-19f1-4953-a115-3b711768f9bf, +bg009,7459ec1c-07dd-43fd-89ad-7cd5a6acf704, +bg010,d464e672-3b16-4b18-966e-55822d831023, +bg011,8a8558b8-da63-4efe-90ee-42da6aa742c4, +bg012,9aac6c96-38d4-4869-a255-bffa2a941bd7, +bg013,a7afb95b-d57d-4530-86e9-4930e2eaaff5, +bg014,17369ad5-64fb-43c6-a13b-1427187c4711, +bg015,594e5fa6-ebdf-495d-84c7-c4680d5e754b, +bg016,e1cb508a-2644-4a75-9d9f-1458e3e8cf96, +bg017,27a7bfbd-2fef-4254-86d3-60d4e279cfed, +bg018,4d8f37e0-eb2e-4286-a4eb-3fd378ad94cc, +bg019,dc582ac8-1fa8-438f-a791-d486f17c9029, +mu006,f068ffc0-87c5-4caf-b206-dc1ba5516c99, +th998,141f4a98-09bf-4a52-b42e-571fadcc53eb, +ig045,a8b3d9ff-839d-4016-9ce6-9d9bb63d1d36, +ig046,a2e9997b-64a8-4874-b0c5-17093441a2f5, +th006,2bd08a14-c84e-47d2-b0d5-e9f0c8937b98, +ig047,a210e6d6-82af-4081-aeae-196e6b3528c0, +th008,cd740e0a-d10c-47cc-a7b8-bff2d03977cb, +th009,a0b39026-4615-4087-b3de-4bced4ec926d, +th010,013cfef7-029c-448d-9740-e649b89baa28, +th011,caf1bf2d-9e04-4b12-a13b-502fe9abbd99, +th012,3689be3f-33bd-4672-b78c-5ea4d46cd15d, +th013,c8ea8eac-b71d-446e-827b-e6092892a086, +th014,64af0cbc-ba40-4794-8197-496a2109d4ec, +th015,b3d4fc6b-4aba-4c80-8bd0-5b4fce6bf57b, +th016,513f56cc-a5d9-4bd1-8209-29b2b87fac53, +th017,0803f23d-587b-443d-a9c4-0fce063be8fc, +th018,7abbeec8-9a22-4f83-9116-14274cf64479, +th019,78ef3012-f687-46a1-ad64-a8ae20f33319, +ig049,c37a2a37-e42b-4adf-85f4-86509032b42d, +ig050,1368f1bd-a407-4273-ae4b-db6bec4d2cf9, +ig051,a64f5b47-5e2e-442d-95e3-2be7c7071253, +ig052,a419e87c-6187-4a2f-bd22-e86c92153395, diff --git a/titles/cxb/rss1.py b/titles/cxb/rss1.py new file mode 100644 index 0000000..e480238 --- /dev/null +++ b/titles/cxb/rss1.py @@ -0,0 +1,257 @@ +import json +from decimal import Decimal +from base64 import b64encode +from typing import Any, Dict +from hashlib import md5 +from datetime import datetime + +from core.config import CoreConfig +from core.data import Data, cached +from titles.cxb.config import CxbConfig +from titles.cxb.base import CxbBase +from titles.cxb.const import CxbConstants + +class CxbRevSunriseS1(CxbBase): + def __init__(self, cfg: CoreConfig, game_cfg: CxbConfig) -> None: + super().__init__(cfg, game_cfg) + self.version = CxbConstants.VER_CROSSBEATS_REV_SUNRISE_S1 + + def handle_data_path_list_request(self, data: Dict) -> Dict: + return { "data": "" } + + @cached(lifetime=86400) + def handle_data_music_list_request(self, data: Dict) -> Dict: + ret_str = "" + with open(r"titles/cxb/rss1_data/MusicArchiveList.csv") as music: + lines = music.readlines() + for line in lines: + line_split = line.split(',') + ret_str += f"{line_split[0]},{line_split[1]},{line_split[2]},{line_split[3]},{line_split[4]},{line_split[5]},{line_split[6]},{line_split[7]},{line_split[8]},{line_split[9]},{line_split[10]},{line_split[11]},{line_split[12]},{line_split[13]},{line_split[14]},\r\n" + + return({"data":ret_str}) + + @cached(lifetime=86400) + def handle_data_item_list_detail_request(self, data: Dict) -> Dict: + #ItemListIcon load + ret_str = "#ItemListIcon\r\n" + with open(r"titles/cxb/rss1_data/Item/ItemList_Icon.csv", encoding="shift-jis") as item: + lines = item.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + + #ItemListTitle load + ret_str += "\r\n#ItemListTitle\r\n" + with open(r"titles/cxb/rss1_data/Item/ItemList_Title.csv", encoding="shift-jis") as item: + lines = item.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + + return({"data":ret_str}) + + @cached(lifetime=86400) + def handle_data_shop_list_detail_request(self, data: Dict) -> Dict: + #ShopListIcon load + ret_str = "#ShopListIcon\r\n" + with open(r"titles/cxb/rss1_data/Shop/ShopList_Icon.csv", encoding="utf-8") as shop: + lines = shop.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + + #ShopListMusic load + ret_str += "\r\n#ShopListMusic\r\n" + with open(r"titles/cxb/rss1_data/Shop/ShopList_Music.csv", encoding="utf-8") as shop: + lines = shop.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + + #ShopListSale load + ret_str += "\r\n#ShopListSale\r\n" + with open(r"titles/cxb/rss1_data/Shop/ShopList_Sale.csv", encoding="shift-jis") as shop: + lines = shop.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + + #ShopListSkinBg load + ret_str += "\r\n#ShopListSkinBg\r\n" + with open(r"titles/cxb/rss1_data/Shop/ShopList_SkinBg.csv", encoding="shift-jis") as shop: + lines = shop.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + + #ShopListSkinEffect load + ret_str += "\r\n#ShopListSkinEffect\r\n" + with open(r"titles/cxb/rss1_data/Shop/ShopList_SkinEffect.csv", encoding="shift-jis") as shop: + lines = shop.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + + #ShopListSkinNotes load + ret_str += "\r\n#ShopListSkinNotes\r\n" + with open(r"titles/cxb/rss1_data/Shop/ShopList_SkinNotes.csv", encoding="shift-jis") as shop: + lines = shop.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + + #ShopListTitle load + ret_str += "\r\n#ShopListTitle\r\n" + with open(r"titles/cxb/rss1_data/Shop/ShopList_Title.csv", encoding="utf-8") as shop: + lines = shop.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + return({"data":ret_str}) + + def handle_data_extra_stage_list_request(self, data: Dict) -> Dict: + return({"data":""}) + + def handle_data_ex0001_request(self, data: Dict) -> Dict: + return({"data":""}) + + def handle_data_one_more_extra_list_request(self, data: Dict) -> Dict: + return({"data":""}) + + def handle_data_bonus_list10100_request(self, data: Dict) -> Dict: + return({"data":""}) + + def handle_data_oe0001_request(self, data: Dict) -> Dict: + return({"data":""}) + + def handle_data_free_coupon_request(self, data: Dict) -> Dict: + return({"data":""}) + + @cached(lifetime=86400) + def handle_data_news_list_request(self, data: Dict) -> Dict: + ret_str = "" + with open(r"titles/cxb/rss1_data/NewsList.csv", encoding="UTF-8") as news: + lines = news.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + return({"data":ret_str}) + + def handle_data_tips_request(self, data: Dict) -> Dict: + return({"data":""}) + + def handle_data_release_info_list_request(self, data: Dict) -> Dict: + return({"data":""}) + + @cached(lifetime=86400) + def handle_data_random_music_list_request(self, data: Dict) -> Dict: + ret_str = "" + with open(r"titles/cxb/rss1_data/MusicArchiveList.csv") as music: + lines = music.readlines() + count = 0 + for line in lines: + line_split = line.split(",") + ret_str += str(count) + "," + line_split[0] + "," + line_split[0] + ",\r\n" + + return({"data":ret_str}) + + @cached(lifetime=86400) + def handle_data_license_request(self, data: Dict) -> Dict: + ret_str = "" + with open(r"titles/cxb/rss1_data/License.csv", encoding="UTF-8") as licenses: + lines = licenses.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + return({"data":ret_str}) + + @cached(lifetime=86400) + def handle_data_course_list_request(self, data: Dict) -> Dict: + ret_str = "" + with open(r"titles/cxb/rss1_data/Course/CourseList.csv", encoding="UTF-8") as course: + lines = course.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + return({"data":ret_str}) + + @cached(lifetime=86400) + def handle_data_csxxxx_request(self, data: Dict) -> Dict: + extra_num = int(data["dldate"]["filetype"][-4:]) + ret_str = "" + with open(fr"titles/cxb/rss1_data/Course/Cs{extra_num}.csv", encoding="shift-jis") as course: + lines = course.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + return({"data":ret_str}) + + def handle_data_mission_list_request(self, data: Dict) -> Dict: + return({"data":""}) + + def handle_data_mission_bonus_request(self, data: Dict) -> Dict: + return({"data":""}) + + def handle_data_unlimited_mission_request(self, data: Dict) -> Dict: + return({"data":""}) + + def handle_data_partner_list_request(self, data: Dict) -> Dict: + ret_str = "" + # Lord forgive me for the sins I am about to commit + for i in range(0,10): + ret_str += f"80000{i},{i},{i},0,10000,,\r\n" + ret_str += f"80000{i},{i},{i},1,10500,,\r\n" + ret_str += f"80000{i},{i},{i},2,10500,,\r\n" + for i in range(10,13): + ret_str += f"8000{i},{i},{i},0,10000,,\r\n" + ret_str += f"8000{i},{i},{i},1,10500,,\r\n" + ret_str += f"8000{i},{i},{i},2,10500,,\r\n" + ret_str +="\r\n---\r\n0,150,100,100,100,100,\r\n" + for i in range(1,130): + ret_str +=f"{i},100,100,100,100,100,\r\n" + + ret_str += "---\r\n" + return({"data": ret_str}) + + @cached(lifetime=86400) + def handle_data_partnerxxxx_request(self, data: Dict) -> Dict: + partner_num = int(data["dldate"]["filetype"][-4:]) + ret_str = f"{partner_num},,{partner_num},1,10000,\r\n" + with open(r"titles/cxb/rss1_data/Partner0000.csv") as partner: + lines = partner.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + return({"data": ret_str}) + + def handle_data_server_state_request(self, data: Dict) -> Dict: + return({"data": True}) + + def handle_data_settings_request(self, data: Dict) -> Dict: + return({"data": "2,\r\n"}) + + def handle_data_story_list_request(self, data: Dict) -> Dict: + #story id, story name, game version, start time, end time, course arc, unlock flag, song mcode for menu + ret_str = "\r\n" + ret_str += f"st0000,RISING PURPLE,10104,1464370990,4096483201,Cs1000,-1,purple,\r\n" + ret_str += f"st0001,REBEL YELL,10104,1467999790,4096483201,Cs1000,-1,chaset,\r\n" + ret_str += f"st0002,REMNANT,10104,1502127790,4096483201,Cs1000,-1,overcl,\r\n" + return({"data": ret_str}) + + def handle_data_stxxxx_request(self, data: Dict) -> Dict: + story_num = int(data["dldate"]["filetype"][-4:]) + ret_str = "" + for i in range(1,11): + ret_str +=f"{i},st000{story_num}_{i-1},,,,,,,,,,,,,,,,1,,-1,1,\r\n" + return({"data": ret_str}) + + def handle_data_event_stamp_list_request(self, data: Dict) -> Dict: + return({"data":"Cs1032,1,1,1,1,1,1,1,1,1,1,\r\n"}) + + def handle_data_premium_list_request(self, data: Dict) -> Dict: + return({"data": "1,,,,10,,,,,99,,,,,,,,,100,,\r\n"}) + + def handle_data_event_list_request(self, data: Dict) -> Dict: + return({"data":""}) + + def handle_data_event_detail_list_request(self, data: Dict) -> Dict: + event_id = data["dldate"]["filetype"].split("/")[2] + if "EventStampMapListCs1002" in event_id: + return({"data":"1,2,1,1,2,3,9,5,6,7,8,9,10,\r\n"}) + elif "EventStampList" in event_id: + return({"data":"Cs1002,1,1,1,1,1,1,1,1,1,1,\r\n"}) + else: + return({"data":""}) + + def handle_data_event_stamp_map_list_csxxxx_request(self, data: Dict) -> Dict: + event_id = data["dldate"]["filetype"].split("/")[2] + if "EventStampMapListCs1002" in event_id: + return({"data":"1,2,1,1,2,3,9,5,6,7,8,9,10,\r\n"}) + else: + return({"data":""}) diff --git a/titles/cxb/rss1_data/Course/CourseList.csv b/titles/cxb/rss1_data/Course/CourseList.csv new file mode 100644 index 0000000..845bc50 --- /dev/null +++ b/titles/cxb/rss1_data/Course/CourseList.csv @@ -0,0 +1,5 @@ +Cs0000,0,10001,1422284400,4096483201,0,-1,-1,100,5,クリアã™ã‚‹ã¨æ®µä½ãŒã€Œè¦‹ç¿’ã„ã€ã«æ˜‡æ ¼ã—ã¾ã™ã€‚,-1, +Cs0001,1,10001,1422284400,4096483201,0,-1,-1,110,5,クリアã™ã‚‹ã¨æ®µä½ãŒã€Œåˆæ®µã€ã«æ˜‡æ ¼ã—ã¾ã™ã€‚,-1, +Cs0002,2,10001,1422284400,4096483201,0,-1,-1,120,5,クリアã™ã‚‹ã¨æ®µä½ãŒã€ŒäºŒæ®µã€ã«æ˜‡æ ¼ã—ã¾ã™ã€‚,-1, +Cs0003,3,10001,1422284400,4096483201,0,-1,-1,130,5,クリアã™ã‚‹ã¨æ®µä½ãŒã€Œä¸‰æ®µã€ã«æ˜‡æ ¼ã—ã¾ã™ã€‚,-1, +Cs0004,4,10001,1422284400,4096483201,0,-1,-1,140,5,クリアã™ã‚‹ã¨æ®µä½ãŒã€Œå››æ®µã€ã«æ˜‡æ ¼ã—ã¾ã™ã€‚,-1, \ No newline at end of file diff --git a/titles/cxb/rss1_data/Course/Cs0000.csv b/titles/cxb/rss1_data/Course/Cs0000.csv new file mode 100644 index 0000000..47a0fc3 --- /dev/null +++ b/titles/cxb/rss1_data/Course/Cs0000.csv @@ -0,0 +1,4 @@ +1,okonik,4,-1,-,-1,-1,-1,-1,-1,-1,-1, +2,okonik,0,-1,-,-1,-1,-1,-1,-1,-1,-1, +3,okonik,1,-1,-,-1,-1,-1,-1,-1,-1,-1, +4,okonik,3,-1,-,-1,-1,-1,-1,-1,-1,-1, diff --git a/titles/cxb/rss1_data/Course/Cs0001.csv b/titles/cxb/rss1_data/Course/Cs0001.csv new file mode 100644 index 0000000..47a0fc3 --- /dev/null +++ b/titles/cxb/rss1_data/Course/Cs0001.csv @@ -0,0 +1,4 @@ +1,okonik,4,-1,-,-1,-1,-1,-1,-1,-1,-1, +2,okonik,0,-1,-,-1,-1,-1,-1,-1,-1,-1, +3,okonik,1,-1,-,-1,-1,-1,-1,-1,-1,-1, +4,okonik,3,-1,-,-1,-1,-1,-1,-1,-1,-1, diff --git a/titles/cxb/rss1_data/Course/Cs0002.csv b/titles/cxb/rss1_data/Course/Cs0002.csv new file mode 100644 index 0000000..47a0fc3 --- /dev/null +++ b/titles/cxb/rss1_data/Course/Cs0002.csv @@ -0,0 +1,4 @@ +1,okonik,4,-1,-,-1,-1,-1,-1,-1,-1,-1, +2,okonik,0,-1,-,-1,-1,-1,-1,-1,-1,-1, +3,okonik,1,-1,-,-1,-1,-1,-1,-1,-1,-1, +4,okonik,3,-1,-,-1,-1,-1,-1,-1,-1,-1, diff --git a/titles/cxb/rss1_data/Course/Cs0003.csv b/titles/cxb/rss1_data/Course/Cs0003.csv new file mode 100644 index 0000000..47a0fc3 --- /dev/null +++ b/titles/cxb/rss1_data/Course/Cs0003.csv @@ -0,0 +1,4 @@ +1,okonik,4,-1,-,-1,-1,-1,-1,-1,-1,-1, +2,okonik,0,-1,-,-1,-1,-1,-1,-1,-1,-1, +3,okonik,1,-1,-,-1,-1,-1,-1,-1,-1,-1, +4,okonik,3,-1,-,-1,-1,-1,-1,-1,-1,-1, diff --git a/titles/cxb/rss1_data/Course/Cs0004.csv b/titles/cxb/rss1_data/Course/Cs0004.csv new file mode 100644 index 0000000..47a0fc3 --- /dev/null +++ b/titles/cxb/rss1_data/Course/Cs0004.csv @@ -0,0 +1,4 @@ +1,okonik,4,-1,-,-1,-1,-1,-1,-1,-1,-1, +2,okonik,0,-1,-,-1,-1,-1,-1,-1,-1,-1, +3,okonik,1,-1,-,-1,-1,-1,-1,-1,-1,-1, +4,okonik,3,-1,-,-1,-1,-1,-1,-1,-1,-1, diff --git a/titles/cxb/rss1_data/Ex0000.csv b/titles/cxb/rss1_data/Ex0000.csv new file mode 100644 index 0000000..8658453 --- /dev/null +++ b/titles/cxb/rss1_data/Ex0000.csv @@ -0,0 +1,10 @@ +StageMax,ClearCount,StageNo.,MCODE,Diff(std),Diff(hrd),Diff(mas),Diff(ulm),Diff(esy),Level(op), Level(val), Grade(op),Grade(val),GT(nml),GT(svl),GT(ult),GT(nhp),GT(esy),HS(op),HS(val),APP,DAP,F-V,F-H,FT(ac),FT(tab),FT(pho),Combo(op),Combo(val),FullCombo,ClearRate(op),ClearRate(val) +2,1,1,-,2,2,2,1,2,-1,-,1,1,-1,-1,-1,-1,-1,1,-2,-1,-1,-1,-1,-1,-1,-1,1,300,-1,-1,-, +2,-,2,-,2,2,2,1,2,-1,-,1,1,-1,-1,-1,-1,-1,1,-2,-1,-1,-1,-1,-1,-1,-1,1,300,-1,-1,-, +3,3,1,-,2,2,1,1,2,-1,-,1,2,-1,-1,-1,-1,-1,1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-,-1,-1,-, +3,-,2,-,2,2,1,1,2,-1,-,1,2,-1,-1,-1,-1,-1,1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-,-1,-1,-, +3,-,3,-,2,2,1,1,2,1,50,1,2,2,2,1,2,2,1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-,-1,-1,-, +4,4,1,dynami:fronti:rebell:fronti2:auflcb:auflcb2,2,2,1,1,2,-1,-,1,-1,-1,-1,-1,-1,-1,1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-,-1,-1,-, +4,-,2,-,2,2,1,1,2,-1,-,1,-1,-1,-1,-1,-1,-1,1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-,-1,-1,-, +4,-,3,-,2,2,1,1,2,-1,-,1,-1,-1,-1,-1,-1,-1,1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-,-1,-1,-, +4,-,4,-,2,2,1,1,2,-1,-,1,-1,-1,-1,-1,-1,-1,1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-,-1,-1,-, diff --git a/titles/cxb/rss1_data/ExtraStageList.csv b/titles/cxb/rss1_data/ExtraStageList.csv new file mode 100644 index 0000000..30e1b23 --- /dev/null +++ b/titles/cxb/rss1_data/ExtraStageList.csv @@ -0,0 +1,2 @@ +No.,Ver.,ESID,Title,StartTime,EndTime,出ç¾æ›²MCODE,出ç¾RP,出ç¾ã‚¨ãƒ•ã‚§ã‚¯ãƒˆ,Diff(std),Diff(hrd),Diff(mas),Diff(ulm),Diff(esy),GT(nml),GT(svl),GT(ult),GT(nhp),GT(esy),HS(op),HS(val),APP,DAP,F-V,F-H,FT(ac),FT(tab),FT(pho) +1,1000,Ex0000,MEGALOMAN[i]A出ç¾,1411697520.0288,1443233520.0288,megaro,0,3,2,2,1,2,2,2,2,1,2,2,1,-2,-1,-1,-1,-1,-1,-1,-1, diff --git a/titles/cxb/rss1_data/Item/ItemList_Icon.csv b/titles/cxb/rss1_data/Item/ItemList_Icon.csv new file mode 100644 index 0000000..f61eb72 --- /dev/null +++ b/titles/cxb/rss1_data/Item/ItemList_Icon.csv @@ -0,0 +1,3 @@ +1000,ic0000,IconName0000,, +1001,ic0001,IconName0001,, +1002,ic0002,IconName0002,, diff --git a/titles/cxb/rss1_data/Item/ItemList_Title.csv b/titles/cxb/rss1_data/Item/ItemList_Title.csv new file mode 100644 index 0000000..2334385 --- /dev/null +++ b/titles/cxb/rss1_data/Item/ItemList_Title.csv @@ -0,0 +1,3 @@ +2000,ƒX[ƒp[ƒSƒŠƒ‰,4, +2001,‚ӂ‚¤‚̃SƒŠƒ‰,3, +2002,ƒ‚ƒ“ƒL[,0, diff --git a/titles/cxb/rss1_data/License.csv b/titles/cxb/rss1_data/License.csv new file mode 100644 index 0000000..8e5f1b7 --- /dev/null +++ b/titles/cxb/rss1_data/License.csv @@ -0,0 +1,89 @@ +英雄ã®è¨¼ ~ 4Version/カプコンサウンドãƒãƒ¼ãƒ  +© CAPCOM CO., LTD. ALL RIGHTS RESERVED. + +ç¼ç†±ã®åˆƒ ~ ディノãƒãƒ«ãƒ‰ +© CAPCOM CO., LTD. ALL RIGHTS RESERVED. + +å¤ä»£ã®æ¯å¹ã +© CAPCOM CO., LTD. ALL RIGHTS RESERVED. + +Theme of Ryu -SFIV Arrange-/Capcom Sound Team / Hideyuki Fukasawa +©CAPCOM U.S.A., INC. ALL RIGHTS RESERVED. + +Ultra Street Fighter IV/Hideyuki Fukasawa +©CAPCOM U.S.A., INC. ALL RIGHTS RESERVED. + +Theme of Chun-Li -SFIV Arrange-/Capcom Sound Team / Hideyuki Fukasawa +©CAPCOM U.S.A., INC. ALL RIGHTS RESERVED. + +Street Fighter V/Masahiro Aoki +©CAPCOM U.S.A., INC. ALL RIGHTS RESERVED. + +英雄ã®è¨¼ï¼MHF-G 2015 Version +© CAPCOM CO., LTD. ALL RIGHTS RESERVED. + +異ヲ辿リシモノ -対峙-/若林タカツグ +© CAPCOM CO., LTD. ALL RIGHTS RESERVED. + + + + + + + + + + + + + +QLWA(グルーヴコースター 3 リンクフィーãƒãƒ¼ã‚ˆã‚Šï¼‰/t+pazolite +© TAITO CORPORATION 1978,2016 ALL RIGHTS RESERVED. + +グルーヴ・ザ・ãƒãƒ¼ãƒˆï¼ˆã‚°ãƒ«ãƒ¼ãƒ´ã‚³ãƒ¼ã‚¹ã‚¿ãƒ¼ 3 リンクフィーãƒãƒ¼ã‚ˆã‚Šï¼‰/ビートã¾ã‚ŠãŠï¼‹ã‚ã¾ã­ +© TAITO CORPORATION 1978,2016 ALL RIGHTS RESERVED. + +Got hive of Ra(グルーヴコースター 3 リンクフィーãƒãƒ¼ã‚ˆã‚Šï¼‰/E.G.G. +© TAITO CORPORATION 1978,2016 ALL RIGHTS RESERVED. + +LINK LINK FEVER!!!(グルーヴコースター 3 リンクフィーãƒãƒ¼ã‚ˆã‚Šï¼‰/リンカ (CV:豊田èŒçµµï¼‰ +© TAITO CORPORATION 1978,2016 ALL RIGHTS RESERVED. + +SAKURA EXHAUST/RIO HAMAMOTO(BNSI)「太鼓ã®é”人ã€ã‚ˆã‚Š +©BANDAI NAMCO Entertainment Inc. + +カリソメ(グルーヴコースター 3 リンクフィーãƒãƒ¼ã‚ˆã‚Šï¼‰/コンプ(豚乙女) × ichigo(岸田教団 & THE明星ロケッツ) +©上海アリス幻樂団 +© TAITO CORPORATION 1978,2016 ALL RIGHTS RESERVED. + +ãã—ã¦èª°ã‚‚ã„ãªããªã£ãŸï¼ˆã‚°ãƒ«ãƒ¼ãƒ´ã‚³ãƒ¼ã‚¹ã‚¿ãƒ¼ 3 リンクフィーãƒãƒ¼ã‚ˆã‚Šï¼‰/コãƒãƒ¤ã‚·ãƒ¦ã‚¦ãƒ¤(IOSYS) × ã‚ã«ãƒ¼(TaNaBaTa) +©上海アリス幻樂団 +© TAITO CORPORATION 1978,2016 ALL RIGHTS RESERVED. + + + + + + + + + + + + + + + + + +Font Design by Fontworks Inc. +DynaFont is a registered Trademark of DynaComware Taiwan Inc. +Ogg Vorbis is Copyright ©2015, Xiph. Org Foundation +The font used on this product is provided by Hakusyu Fonts co,.Ltd. +キャスティング・ライツクリアランス +æ ªå¼ä¼šç¤¾ãƒ“ーイング +JASRAC許諾第V-1512134å· +e-License許諾番å·GS35000 +VOCALOID and VOCALO are trademarks of Yamaha Corporation. + +OMNiMIX v1.2 \ No newline at end of file diff --git a/titles/cxb/rss1_data/MissionList.csv b/titles/cxb/rss1_data/MissionList.csv new file mode 100644 index 0000000..979d4ec --- /dev/null +++ b/titles/cxb/rss1_data/MissionList.csv @@ -0,0 +1,4 @@ +MissionID,Text,Type,Value_op,Value,Mcode,Difficulty_op,Difficulty,Level_op,Level,Grade_op,Grade,GaugeType_op,GaugeType,HiSpeed_op,HiSpeed,APP,DAP,FlipV,FlipH,Fullcombo,Combo_op,Combo,ClearRate_op,ClearRate,StartTime,StartEnd,District,CoupleId +0,-,-1,-1,-1,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +1,-,-1,-1,-1,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +2,ExtraStageã§MEGALOMAN[i]Aをクリアã™ã‚‹,0,-1,-1,megaro,-1,-1,-1,-1,1,9,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, \ No newline at end of file diff --git a/titles/cxb/rss1_data/MusicArchiveList.csv b/titles/cxb/rss1_data/MusicArchiveList.csv new file mode 100644 index 0000000..7b9d5ae --- /dev/null +++ b/titles/cxb/rss1_data/MusicArchiveList.csv @@ -0,0 +1,472 @@ +tutori2,10000,100000,10,10,10,10,10,0,0,0,0,0,0,100000, +tutori3,10000,100000,10,10,10,10,10,0,0,0,0,0,0,100000, +tutori4,10000,100000,10,10,10,10,10,0,0,0,0,0,0,100000, +tutori6,10000,100000,10,10,10,10,10,0,0,0,0,0,0,100000, +tutori8,10000,1000000,30,150,350,0,0,1,1,1,0,0,0,100345, +sateli,10000,100000,280,490,770,820,170,1,1,1,1,1,0,100300, +nature,10000,100000,140,240,530,750,50,1,1,1,1,1,3,100301, +purple,10000,100000,220,540,640,730,140,1,1,1,1,1,0,100307, +hearts,10000,100000,180,380,680,770,80,1,1,1,1,1,0,100308, +phasea,10000,100000,160,380,650,750,90,1,1,1,1,1,0,100310, +planet,10000,100000,170,360,490,710,100,1,1,1,1,1,1,100311, +firefo,10000,100000,130,360,570,830,70,1,1,1,1,1,0,100314, +kounen,10000,100000,210,400,660,780,100,1,1,1,1,1,3,100315, +essenc,10000,100000,250,500,700,760,110,1,1,1,1,1,0,100316, +summer,10000,100000,230,570,790,890,130,1,1,1,1,1,0,100317, +tanosi,10000,100000,250,450,700,800,160,1,1,1,1,1,7,100319, +picora,10000,100000,150,380,660,750,80,1,1,1,1,1,1,100320, +devils,10000,100000,270,400,800,0,150,1,1,1,0,1,0,100323, +techno,10000,100000,160,380,510,740,90,1,1,1,1,1,1,100328, +glowww,10000,100000,170,280,420,600,80,1,1,1,1,1,7,100335, +powerr,10000,100000,190,380,690,790,120,1,1,1,1,1,0,100336, +amater,10000,100000,210,480,650,790,130,1,1,1,1,1,0,100340, +advers,10000,100000,150,480,710,830,90,1,1,1,1,1,1,100349, +venera,10000,100000,150,430,680,750,80,1,1,1,1,1,0,100353, +dazaii,10000,100000,210,430,730,770,120,1,1,1,1,1,3,100357, +thesig,10000,100000,210,380,560,730,100,1,1,1,1,1,3,100365, +hosita,10000,100000,160,360,480,650,100,1,1,1,1,1,9999,100344, +bluede,10000,100000,220,410,580,700,120,1,1,1,1,1,5,100372, +emerao,10000,100000,300,530,850,0,190,1,1,1,0,1,1,100373, +megaro,10000,100000,550,800,930,980,0,1,1,1,1,0,0,100129, +angeli,10000,100000,330,560,820,900,220,1,1,1,1,1,0,100330, +moonli,10000,100000,140,430,610,730,80,1,1,1,1,1,9999,100342, +yumemi,10000,100000,120,350,590,690,60,1,1,1,1,1,9999,100369, +pinkym,10000,100000,240,440,740,810,160,1,1,1,1,1,0,100348, +dynami2,10000,100000,180,510,780,800,80,1,1,1,1,1,0,100370, +reseed3,10000,100000,200,550,760,800,100,1,1,1,1,1,0,100306, +toucho,10000,200000,90,280,440,650,50,1,1,1,1,1,1,100002, +ameoto,10000,200000,120,260,470,630,60,1,1,1,1,1,1,100003, +kimito,10000,200000,100,260,490,660,70,1,1,1,1,1,1,100004, +giantk,10000,200000,250,530,710,780,110,1,1,1,1,1,3,100021, +breakd,10000,200000,230,340,570,740,110,1,1,1,1,1,2,100015, +dazzlj,10000,200000,350,600,800,900,160,1,1,1,1,1,1,100028, +ididid,10000,200000,290,460,720,810,160,1,1,1,1,1,1,100093, +sundro,10000,200000,240,470,750,830,140,1,1,1,1,1,3,100042, +auflcb,10000,200000,130,430,810,0,80,1,1,1,0,1,0,100063, +dennou,10000,200000,290,600,760,870,150,1,1,1,1,1,1,100045, +hokoro,10000,200000,290,570,710,810,140,1,1,1,1,1,1,100068, +landin,10000,200000,260,330,490,670,130,1,1,1,1,1,1,100005, +tomorr,10000,100000,150,240,440,620,80,1,1,1,1,1,7,100362, +daybyd,10000,100000,130,260,380,590,60,1,1,1,1,1,7,100363, +syoujo,10000,100000,190,350,530,780,80,1,1,1,1,1,1,100309, +destru,10000,100000,190,410,620,720,100,1,1,1,1,1,0,100352, +gingat,10000,200000,130,290,460,610,50,1,1,1,1,1,0,100041, +daisak,10000,200000,280,360,600,750,120,1,1,1,1,1,0,100066, +paradi,10000,100000,160,280,530,640,100,1,1,1,1,1,3,100376, +pigooo,10000,100000,190,340,590,840,130,1,1,1,1,1,6,100377, +season,10000,300000,150,280,440,650,80,1,1,1,1,1,1,100386, +canonn,10000,300000,170,280,500,830,70,1,1,1,1,1,1,100387, +rhapso,10000,300000,180,340,620,740,60,1,1,1,1,1,1,100388, +turkis,10000,300000,190,390,640,840,110,1,1,1,1,1,1,100389, +biohaz,10000,300000,150,300,510,640,60,1,1,1,1,1,9999,100390, +monhan,10000,300000,100,260,360,540,50,1,1,1,1,1,9999,100391, +gyakut2,10000,300000,130,350,430,560,50,1,1,1,1,1,9999,100392, +street,10000,300000,130,340,470,660,70,1,1,1,1,1,9999,100393, +rockma2,10000,300000,210,340,490,760,140,1,1,1,1,1,9999,100394, +auflcb3,10000,100000,160,360,660,860,60,1,1,1,1,1,9999,100374, +irohaa,10000,100000,190,410,550,760,120,1,1,1,1,1,0,100325, +ibelie,10000,100000,270,470,780,820,140,1,1,1,1,1,0,100326, +monhan2,10000,300000,120,240,430,680,60,1,1,1,1,1,9999,100409, +monhan3,10000,300000,180,280,450,730,80,1,1,1,1,1,9999,100410, +yejiii,10000,100000,220,360,630,790,100,1,1,1,1,1,0,100418, +histor,10000,100000,200,360,560,820,110,1,1,1,1,1,0,100419, +chaset,10000,100000,150,310,580,760,80,1,1,1,1,1,9999,100338, +metall,10000,100000,160,280,570,770,80,1,1,1,1,1,1,100412, +letmeg,10000,100000,180,320,500,720,120,1,1,1,1,1,1,100327, +hontno,10000,200000,120,260,530,660,90,1,1,1,1,1,1,100010, +azitat,10000,200000,240,550,700,830,140,1,1,1,1,1,0,100024, +hellom,10000,100000,180,370,580,720,70,1,1,1,1,1,1,100360, +laught,10000,100000,200,350,510,710,70,1,1,1,1,1,1,100337, +bluede2,10000,100000,160,360,560,810,90,1,1,1,1,1,1,100426, +street2,10000,300000,210,370,550,730,140,1,1,1,1,1,9999,100423, +street3,10000,300000,240,380,570,740,130,1,1,1,1,1,9999,100424, +street4,10000,300000,170,320,510,780,110,1,1,1,1,1,9999,100425, +silbur,10000,100000,200,350,540,750,90,1,1,1,1,1,1,100421, +spicaa,10000,100000,230,360,560,780,100,1,1,1,1,1,1,100422, +tricko,10000,100000,230,340,560,750,110,1,1,1,1,1,0,100438, +thisis,10000,100000,230,370,600,740,120,1,1,1,1,1,4,100435, +rising,10000,100000,230,380,660,850,140,1,1,1,1,1,4,100436, +orbita,10000,100000,200,380,620,740,120,1,1,1,1,1,5,100411, +dddddd,10000,100000,130,330,530,690,90,1,1,1,1,1,5,100433, +pyroma,10000,100000,220,420,700,840,80,1,1,1,1,1,4,100427, +touchn,10000,100000,270,460,680,860,150,1,1,1,1,1,8,100312, +onlyll,10000,100000,210,320,560,690,130,1,1,1,1,1,9999,100359, +upside,10000,100000,180,270,480,670,80,1,1,1,1,1,1,100313, +istanb,10000,100000,410,490,900,980,230,1,1,1,1,1,3,100322, +memori,10000,100000,260,380,650,840,130,1,1,1,1,1,9999,100371, +straye,10000,100000,250,360,610,730,140,1,1,1,1,1,4,100350, +rearhy,10000,100000,170,320,520,690,70,1,1,1,1,1,9999,100358, +hereco,10000,100000,160,340,510,680,110,1,1,1,1,1,4,100432, +thesun,10000,100000,250,400,720,870,130,1,1,1,1,1,4,100441, +sayona,10000,100000,200,340,530,710,100,1,1,1,1,1,9999,100343, +flameu,10000,100000,180,360,570,650,100,1,1,1,1,1,1,100380, +raidon,10000,100000,300,380,580,870,130,1,1,1,1,1,1,100434, +riseup,10000,100000,180,410,670,770,70,1,1,1,1,1,4,100437, +sunglo,10000,100000,180,390,590,720,100,1,1,1,1,1,7,100431, +kinbos,10000,100000,220,380,640,770,120,1,1,1,1,1,3,100439, +densho,10000,100000,280,420,740,900,170,1,1,1,1,1,5,100430, +aiohoo,10000,300000,180,290,420,660,100,1,1,1,1,1,1,100471, +entert,10000,300000,150,330,540,870,90,1,1,1,1,1,6,100472, +takeit,10000,100000,200,380,650,830,120,1,1,1,1,1,4,100457, +harmon,10000,100000,200,360,490,650,120,1,1,1,1,1,7,100449, +avemar,10000,300000,160,310,530,750,80,1,1,1,1,1,5,100428, +mateki,10000,300000,180,350,540,790,100,1,1,1,1,1,5,100429, +lovech,10000,100000,160,300,460,680,80,1,1,1,1,1,7,100445, +akaihe,10000,300000,210,320,500,690,80,1,1,1,1,1,1,100473, +juicys,10000,300000,180,260,450,830,100,1,1,1,1,1,7,100474, +codena,10000,100000,180,350,480,680,90,1,1,1,1,1,1,100468, +groove,10000,300000,220,400,520,730,100,1,1,1,1,1,103,100475, +kansho,10000,100000,130,270,550,740,70,1,1,1,1,1,9999,100450, +overcl2,10000,100000,230,420,580,740,120,1,1,1,1,1,3,100486, +taikoo,10000,300000,130,390,500,750,60,1,1,1,1,1,104,100483, +groove2,10000,300000,150,400,580,850,90,1,1,1,1,1,9999,100480, +overcl,10000,100000,120,350,570,860,80,1,1,1,1,1,4,100487, +notoss,10000,100000,120,420,650,910,80,1,1,1,1,1,4,100466, +machup,10000,100000,170,320,410,710,90,1,1,1,1,1,6,100447, +groove3,10000,300000,180,340,640,850,100,1,1,1,1,1,105,100488, +groove4,10000,300000,220,350,500,750,120,1,1,1,1,1,106,100489, +everyt,10000,100000,220,300,740,0,130,1,1,1,0,1,5,100482, +lespri,10000,100000,250,570,800,0,130,1,1,1,0,1,5,100465, +groove5,10000,300000,240,370,670,0,140,1,1,1,0,1,0,100491, +honeyo,10000,100000,320,630,880,930,240,1,1,1,1,1,6,100490, +groove6,10000,300000,300,640,790,0,220,1,1,1,0,1,3,100494, +sunglo2,10000,100000,210,360,670,810,110,1,1,1,1,1,6,100495, +fourte,10000,100000,240,500,740,800,140,1,1,1,1,1,5,100498, +monhan4,10000,300000,120,400,510,620,50,1,1,1,1,1,9999,100496, +monhan5,10000,300000,120,350,420,650,100,1,1,1,1,1,9999,100497, +darkpa,10000,100000,260,390,700,840,160,1,1,1,1,1,3,100504, +hervor,10000,100000,280,390,730,810,180,1,1,1,1,1,5,100505, +cirnon,10000,300000,240,400,600,790,170,1,1,1,1,1,9999,100499, +marisa,10000,300000,250,410,620,850,180,1,1,1,1,1,9999,100500, +yakini,10000,300000,250,340,580,820,130,1,1,1,1,1,9999,100501, +justic,10000,300000,190,360,570,830,140,1,1,1,1,1,1,100502, +sintyo,10000,300000,250,460,700,830,160,1,1,1,1,1,6,100503, +ascand,10000,100000,320,540,800,900,180,1,1,1,1,1,0,100347, +blackl,10000,100000,190,410,730,840,120,1,1,1,1,1,3,100506, +childr,10000,200000,240,390,560,620,140,1,1,1,1,1,0,100043, +tsukai,10000,200000,190,440,720,760,130,1,1,1,1,1,1,100044, +rideon,10000,200000,290,410,600,800,160,1,1,1,1,1,1,100067, +minest,10000,100000,210,390,620,760,130,1,1,1,1,1,1,100507, +ordine,10000,100000,250,430,730,820,190,1,1,1,1,1,3,100508, +dreamw,10000,100000,260,370,620,750,160,1,1,1,1,1,0,100509, +minerv,10000,100000,320,610,900,0,250,1,1,1,0,1,4,100510, +wannab,10000,200000,90,230,400,650,50,1,1,1,1,1,3,100001, +sekain,10000,100000,260,390,690,780,160,1,1,1,1,1,1,100511, +farawa,10000,100000,230,360,600,760,180,1,1,1,1,1,7,100512, +crissc,10000,200000,370,630,860,910,170,1,1,1,1,1,4,100100, +speedy,10000,100000,220,550,770,0,110,1,1,1,0,1,8,100324, +xxxrev,10000,100000,210,340,560,730,150,1,1,1,1,1,0,100513, +higame,10000,200000,200,300,580,710,130,1,1,1,1,1,3,100016, +theepi,10000,200000,190,400,610,750,140,1,1,1,1,1,3,100022, +anomie,10000,200000,220,380,610,770,150,1,1,1,1,1,0,100023, +crocus,10000,100000,260,370,600,720,150,1,1,1,1,1,7,100524, +lavien,10000,100000,270,410,710,800,180,1,1,1,1,1,5,100546, +megaro2,10000,100000,0,0,990,1000,0,0,0,1,1,0,4,100361, +chipnn,10000,100000,270,340,610,790,160,1,1,1,1,1,6,100541, +yiyoyi,10000,200000,140,330,560,700,70,1,1,1,1,1,3,100007, +binary,10000,200000,170,350,640,890,140,1,1,1,1,1,1,100014, +makaim,10000,200000,300,500,770,0,230,1,1,1,0,1,3,100054, +gyakut,10000,200000,150,210,460,640,60,1,1,1,1,1,1,100055, +basara,10000,200000,190,370,640,730,140,1,1,1,1,1,0,100056, +daybre,10000,300000,160,320,530,720,90,1,1,1,1,1,0,100514, +umiyur,10000,300000,140,280,460,640,80,1,1,1,1,1,1,100515, +chalur,10000,300000,180,400,600,720,140,1,1,1,1,1,9999,100516, +melanc,10000,300000,150,300,500,630,100,1,1,1,1,1,7,100517, +konofu,10000,300000,230,350,620,810,110,1,1,1,1,1,1,100518, +bladem,10000,100000,280,380,630,750,170,1,1,1,1,1,4,100526, +southw,10000,100000,180,270,570,680,120,1,1,1,1,1,7,100536, +ryuuse,10000,100000,210,320,590,0,130,1,1,1,0,1,1,100537, +redhea,10000,300000,270,390,590,720,100,1,1,1,1,1,0,100519, +warnin,10000,300000,250,360,610,740,120,1,1,1,1,1,9999,100520, +topsec,10000,300000,240,340,510,640,130,1,1,1,1,1,9999,100521, +dddoll,10000,300000,260,380,550,630,140,1,1,1,1,1,9999,100522, +tracee,10000,300000,190,310,490,650,90,1,1,1,1,1,0,100548, +drivin,10000,200000,230,400,660,760,80,1,1,1,1,1,7,100111, +genzit,10000,200000,180,460,730,820,120,1,1,1,1,1,0,100118, +aerial,10000,200000,110,280,560,710,50,1,1,1,1,1,1,100039, +einher,10000,100000,290,400,740,800,160,1,1,1,1,1,4,100532, +ariell,10000,100000,190,320,640,730,150,1,1,1,1,1,7,100540, +firstl,10000,100000,250,360,650,770,170,1,1,1,1,1,1,100542, +heartl,10000,100000,230,300,640,0,110,1,1,1,0,1,1,100550, +erasee,10000,100000,220,350,580,680,120,1,1,1,1,1,0,100551, +regene,10000,100000,200,300,560,700,130,1,1,1,1,1,0,100530, +allelu,10000,100000,280,350,640,750,160,1,1,1,1,1,9999,100549, +lighto,10000,100000,250,330,600,740,120,1,1,1,1,1,1,100543, +termin,10000,100000,240,340,630,790,130,1,1,1,1,1,7,100552, +ryuuse2,10000,100000,200,360,620,750,130,1,1,1,1,1,1,100556, +prizmm,10000,100000,210,300,540,0,120,1,1,1,0,1,1,100547, +samalv,10000,200000,190,390,580,770,130,1,1,1,1,1,6,100098, +palpit,10000,100000,290,550,840,920,180,1,1,1,1,1,8,100544, +gainen,10000,100000,260,370,630,0,150,1,1,1,0,1,9999,100558, +moonsh,10000,100000,230,360,620,0,100,1,1,1,0,1,3,100525, +moonki,10000,100000,250,390,640,0,130,1,1,1,0,1,1,100559, +moonri,10000,200000,210,380,580,850,140,1,1,1,1,1,0,100560, +goaway,10000,100000,230,450,590,700,100,1,1,1,1,1,0,100561, +itback,10000,100000,230,380,710,0,120,1,1,1,0,1,3,100567, +redhhh,10000,100000,240,390,770,0,130,1,1,1,0,1,4,100569, +actual,10000,100000,250,380,800,0,140,1,1,1,0,1,0,100568, +zonzon,10000,200000,160,330,630,670,50,1,1,1,1,1,1,100367, +memorm,10000,100000,260,370,730,0,150,1,1,1,0,1,0,100565, +kokoro,10000,100000,200,430,650,690,120,1,1,1,1,1,1,100554, +poweri,10000,100000,260,490,750,910,130,1,1,1,1,1,4,100563, +nisenn,10000,100000,0,0,760,0,0,0,0,1,0,0,8,100555, +yukiya,10000,200000,190,400,610,0,110,1,1,1,0,1,3,100096, +zankyo,10000,200000,180,380,570,740,100,1,1,1,1,1,5,100124, +overlp,10000,200000,170,300,510,0,90,1,1,1,0,1,7,100119, +fracta,10000,100000,310,520,830,0,190,1,1,1,0,1,3,100529, +cantst,10000,100000,230,420,650,0,110,1,1,1,0,1,0,100455, +primaa,10000,100000,180,350,540,750,120,1,1,1,1,1,0,100527, +cyberg,10000,100000,230,350,600,0,120,1,1,1,0,1,0,100448, +freakw,10000,200000,220,420,650,660,130,1,1,1,1,1,0,100018, +aquali,10000,200000,160,340,580,0,110,1,1,1,0,1,4,100006, +takesc,10000,100000,270,370,690,0,100,1,1,1,0,1,1,100572, +cthugh,10000,100000,250,480,730,0,140,1,1,1,0,1,0,100531, +thetaa,10000,100000,210,340,620,0,110,1,1,1,0,1,1,100571, +nekofu,10000,300000,220,340,570,800,100,1,1,1,1,1,6,100493, +howtru,10000,200000,120,250,530,740,80,1,1,1,1,1,0,100057, +romanc,10000,200000,280,550,780,0,100,1,1,1,0,1,0,100047, +kotobu,10000,200000,320,710,900,0,250,1,1,1,0,1,0,100573, +xmasss,10000,300000,180,380,560,770,80,1,1,1,1,1,101,100417, +galaxy,10000,300000,160,320,430,670,100,1,1,1,1,1,0,100600, +rebell,10000,1000000,490,630,910,0,0,1,1,1,0,0,0,100601, +anothe,10000,1000000,270,370,730,760,0,1,1,1,1,0,0,100602, +addict,10000,1000000,200,340,520,620,0,1,1,1,1,0,0,100603, +dirtyy,10000,1000000,150,280,590,740,0,1,1,1,1,0,0,100604, +levelf,10000,300000,110,280,450,630,50,1,1,1,1,1,0,100605, +omnive,10000,1000000,340,520,830,860,0,1,1,1,1,0,0,100606, +kakuse,10000,1000000,170,550,750,0,0,1,1,1,0,0,0,100607, +unbeli,10000,300000,130,260,380,620,70,1,1,1,1,1,0,100608, +sonzai,10000,1000000,260,400,590,660,0,1,1,1,1,0,0,100609, +okonik,10000,1000000,260,450,670,0,0,1,1,1,0,0,0,100610, +crssho,10000,1000000,350,600,850,0,100,1,1,1,0,1,0,100611, +reanim,10000,1000000,280,440,700,800,0,1,1,1,1,0,0,100612, +kamino,10000,1000000,400,620,780,0,150,1,1,1,0,1,0,100613, +fiveee,10000,300000,180,370,610,710,100,1,1,1,1,1,0,100614, +granda,10000,1000000,210,380,790,0,0,1,1,1,0,0,0,100615, +fronti2,10000,1000000,460,690,890,0,90,1,1,1,0,1,0,100616, +saigon,10000,1000000,190,310,570,0,0,1,1,1,0,0,0,100617, +replay,10000,300000,180,440,630,700,80,1,1,1,1,1,0,100618, +mousou,10000,1000000,160,260,540,0,0,1,1,1,0,0,0,100619, +aheadd,10000,300000,130,250,350,580,70,1,1,1,1,1,0,100620, +musicr1,10000,100000,220,330,580,740,120,1,1,1,1,1,0,100621, +getthe,10000,300000,170,370,490,660,60,1,1,1,1,1,0,100622, +design,10000,1000000,150,390,680,690,0,1,1,1,1,0,0,100623, +garnet,10000,1000000,260,460,700,940,0,1,1,1,1,0,0,100624, +hopesb,10000,300000,100,250,440,610,70,1,1,1,1,1,0,100625, +shooti,10000,300000,150,370,490,690,70,1,1,1,1,1,0,100626, +dangan,10000,1000000,280,580,810,0,0,1,1,1,0,0,0,100627, +impact,10000,1000000,240,600,720,900,200,1,1,1,1,1,0,100628, +lightm,10000,300000,260,330,540,710,110,1,1,1,1,1,0,100629, +miiroo,10000,300000,220,390,580,680,110,1,1,1,1,1,0,100630, +voiceo,10000,1000000,180,340,580,590,0,1,1,1,1,0,0,100631, +cosmol,10000,1000000,360,640,870,0,250,1,1,1,0,1,0,100632, +vividd,10000,300000,160,350,550,650,90,1,1,1,1,1,0,100633, +splash,10000,1000000,260,500,710,0,0,1,1,1,0,0,0,100634, +donuth,10000,300000,220,400,540,800,110,1,1,1,1,1,0,100635, +senbon,10000,300000,200,280,540,740,120,1,1,1,1,1,0,100636, +kmtyju,10000,300000,240,310,570,740,120,1,1,1,1,1,0,100637, +fronti,10000,1000000,480,650,820,0,130,1,1,1,0,1,0,100638, +nueraa,10000,1000000,220,430,750,530,0,1,1,1,1,0,0,100639, +childe,10000,300000,90,240,340,560,40,1,1,1,1,1,0,100640, +dazzli2,10000,1000000,350,600,820,0,190,1,1,1,0,1,0,100641, +perfec,10000,1000000,390,640,780,0,0,1,1,1,0,0,0,100642, +flower,10000,300000,70,200,400,650,60,1,1,1,1,1,0,100643, +frgmnt,10000,1000000,330,630,740,650,100,1,1,1,1,1,0,100644, +headph,10000,1000000,240,320,520,0,0,1,1,1,0,0,0,100645, +crsang,10000,1000000,270,530,670,0,130,1,1,1,0,1,0,100646, +musicr4,10000,100000,190,320,580,0,120,1,1,1,0,1,0,100647, +imaxim,10000,1000000,440,690,900,870,0,1,1,1,1,0,0,100648, +azitat2,10000,1000000,230,520,660,0,80,1,1,1,0,1,0,100649, +dynami,10000,1000000,260,540,680,0,110,1,1,1,0,1,0,100650, +incave,10000,1000000,220,440,760,780,0,1,1,1,1,0,0,100651, +aktuki,10000,1000000,260,580,840,0,100,1,1,1,0,1,0,100652, +kindof,10000,1000000,140,290,480,0,0,1,1,1,0,0,0,100653, +mikaku,10000,1000000,190,310,540,0,0,1,1,1,0,0,0,100654, +strang,10000,1000000,120,280,550,0,0,1,1,1,0,0,0,100655, +hesper,10000,1000000,360,610,920,930,0,1,1,1,1,0,0,100656, +breaka,10000,300000,150,310,450,680,70,1,1,1,1,1,0,100657, +myname,10000,1000000,60,140,300,570,0,1,1,1,1,0,0,100658, +amaiko,10000,1000000,150,370,600,0,0,1,1,1,0,0,0,100659, +reseed2,10000,1000000,220,470,630,0,0,1,1,1,0,0,0,100660, +kingst,10000,1000000,380,630,740,0,120,1,1,1,0,1,0,100661, +ramram,10000,1000000,230,340,670,0,0,1,1,1,0,0,0,100662, +murasa,10000,1000000,280,410,760,0,0,1,1,1,0,0,0,100663, +happyd,10000,1100000,220,410,730,790,180,1,1,1,1,1,0,100664, +izimed,10000,300000,190,390,690,770,90,1,1,1,1,1,0,100665, +wastel,10000,1000000,40,120,230,400,0,1,1,1,1,0,0,100666, +assign,10000,1000000,260,430,610,620,0,1,1,1,1,0,0,100667, +jahaci,10000,1000000,170,290,590,0,0,1,1,1,0,0,0,100668, +hisuii,10000,1000000,220,470,700,0,0,1,1,1,0,0,0,100669, +godkno,10000,300000,100,260,450,640,60,1,1,1,1,1,0,100670, +roadof,10000,300000,150,360,500,750,70,1,1,1,1,1,0,100671, +rokuch,10000,300000,210,350,620,810,110,1,1,1,1,1,0,100672, +valent,10000,300000,270,330,590,770,100,1,1,1,1,1,0,100673, +unfini,10000,300000,160,320,500,710,80,1,1,1,1,1,0,100674, +auflcb2,10000,1000000,220,370,750,0,100,1,1,1,0,1,0,100675, +burnin,10000,1000000,180,280,600,850,150,1,1,1,1,1,0,100676, +sphere,10000,1000000,200,380,730,0,0,1,1,1,0,0,0,100677, +dropou,10000,300000,170,310,460,690,140,1,1,1,1,1,0,100678, +xencou,10000,300000,200,320,520,600,80,1,1,1,1,1,0,100679, +killyk,10000,300000,130,420,630,760,60,1,1,1,1,1,0,100680, +missil,10000,1000000,160,380,590,0,0,1,1,1,0,0,0,100681, +burstt,10000,300000,120,250,460,630,70,1,1,1,1,1,0,100682, +musicr2,10000,100000,220,330,580,0,120,1,1,1,0,1,0,100683, +isingl,10000,1000000,250,440,800,0,120,1,1,1,0,1,0,100684, +lvless,10000,1000000,230,380,600,0,0,1,1,1,0,0,0,100685, +sapphi,10000,1000000,290,440,810,0,0,1,1,1,0,0,0,100686, +musicr3,10000,100000,190,320,580,720,120,1,1,1,1,1,0,100687, +deeout,10000,1000000,180,340,630,810,0,1,1,1,1,0,0,100688, +sugars,10000,300000,170,300,420,660,60,1,1,1,1,1,0,100689, +mercur,10000,1000000,140,350,660,0,0,1,1,1,0,0,0,100690, +zizizi,10000,1000000,300,570,880,960,0,1,1,1,1,0,0,100691, +wegooo,10000,300000,180,340,540,680,100,1,1,1,1,1,0,100692, +alonee,10000,300000,110,210,360,480,50,1,1,1,1,1,0,100693, +nuheat,10000,1000000,290,440,650,850,0,1,1,1,1,0,0,100694, +granro,10000,300000,150,280,430,600,80,1,1,1,1,1,0,100695, +sister,10000,300000,100,270,460,630,70,1,1,1,1,1,0,100696, +lotusl,10000,1000000,200,360,640,0,0,1,1,1,0,0,0,100697, +yukari,10000,1000000,310,500,760,840,0,1,1,1,1,0,0,100698, +flawli,10000,300000,170,300,400,590,80,1,1,1,1,1,0,100699, +nightf,10000,1000000,150,280,460,710,0,1,1,1,1,0,0,100700, +random,10000,100000,0,0,0,0,0,0,0,0,0,0,0,100701, +wiwwtw,10000,1000000,260,380,620,0,0,1,1,1,0,0,0,100702, +inneru,10000,300000,220,360,480,670,90,1,1,1,1,1,0,100703, +taishi,10000,1000000,190,350,580,0,0,1,1,1,0,0,0,100704, +daysss,10000,1000000,380,590,810,810,0,1,1,1,1,0,0,100705, +bokuwa,10000,300000,230,340,550,690,160,1,1,1,1,1,0,100706, +showww,10000,300000,180,350,510,790,150,1,1,1,1,1,0,100707, +nevers,10000,300000,260,320,650,750,150,1,1,1,1,1,0,100708, +bleeze,10000,300000,160,310,470,620,90,1,1,1,1,1,0,100709, +dreami,10000,1000000,140,370,650,0,0,1,1,1,0,0,0,100710, +allune,10000,1000000,140,350,710,0,0,1,1,1,0,0,0,100711, +always,10000,1000000,130,270,490,0,0,1,1,1,0,0,0,100712, +anomie2,10000,1000000,160,430,840,0,0,1,1,1,0,0,0,100713, +aquali2,10000,1000000,220,430,600,810,0,1,1,1,1,0,0,100714, +astaro,10000,1000000,230,400,740,0,0,1,1,1,0,0,0,100715, +bassan,10000,1000000,200,320,660,0,0,1,1,1,0,0,0,100716, +zonzon2,10000,1000000,130,270,680,750,0,1,1,1,1,0,0,100717, +bouled,10000,1000000,190,300,570,0,0,1,1,1,0,0,0,100718, +brandn,10000,1000000,90,390,660,720,0,1,1,1,1,0,0,100719, +bravee,10000,1000000,350,600,820,0,250,1,1,1,0,-1,0,100720, +breakd2,10000,1000000,340,640,740,0,0,1,1,1,0,0,0,100721, +buffet,10000,1000000,380,550,680,0,300,1,1,1,0,-1,0,100722, +buzzke,10000,1000000,180,330,580,770,0,1,1,1,1,0,0,100723, +cashhh,10000,1000000,190,250,640,0,0,1,1,1,0,0,0,100724, +cloudb,10000,1000000,370,660,740,0,250,1,1,1,0,-1,0,100725, +clouds,10000,1000000,130,250,470,0,0,1,1,1,0,0,0,100726, +codepa,10000,1000000,290,550,700,0,150,1,1,1,0,-1,0,100727, +comear,10000,1000000,380,560,830,0,250,1,1,1,0,-1,0,100728, +crysta,10000,1000000,370,560,810,0,300,1,1,1,0,-1,0,100729, +curseo,10000,1000000,220,360,740,0,0,1,1,1,0,0,0,100730, +datami,10000,1000000,180,360,660,0,0,1,1,1,0,0,0,100731, +defaul,10000,1000000,210,330,480,0,0,1,1,1,0,0,0,100732, +design2,10000,1000000,250,430,680,0,0,1,1,1,0,0,0,100733, +diamon,10000,1000000,100,260,330,0,0,1,1,1,0,0,0,100734, +dispel,10000,1000000,280,480,800,0,0,1,1,1,0,0,0,100735, +distan,10000,1000000,200,300,680,0,0,1,1,1,0,0,0,100736, +dokibl,10000,1000000,150,230,670,0,0,1,1,1,0,0,0,100737, +dontwa,10000,1000000,130,340,690,0,0,1,1,1,0,0,0,100738, +drgirl,10000,1000000,190,350,540,730,0,1,1,1,1,0,0,100739, +eterna,10000,1000000,120,210,390,0,0,1,1,1,0,0,0,100740, +everkr,10000,1000000,180,290,410,0,0,1,1,1,0,0,0,100741, +everwh,10000,1000000,200,310,580,0,0,1,1,1,0,0,0,100742, +farthe,10000,1000000,300,560,780,870,0,1,1,1,1,0,0,100743, +filame,10000,1000000,230,380,630,0,0,1,1,1,0,0,0,100744, +flameu2,10000,1000000,170,240,590,0,0,1,1,1,0,0,0,100745, +freeee,10000,1000000,190,390,690,0,0,1,1,1,0,0,0,100746, +funkyb2,10000,1000000,210,340,560,0,0,1,1,1,0,0,0,100747, +granda2,10000,1000000,240,410,730,830,0,1,1,1,1,0,0,100748, +hsphsp,10000,1000000,120,250,690,0,0,1,1,1,0,0,0,100749, +halluc,10000,1000000,400,520,870,0,0,1,1,1,0,0,0,100750, +indigo,10000,1000000,170,330,500,750,0,1,1,1,1,0,0,100751, +inters,10000,1000000,250,420,770,0,0,1,1,1,0,0,0,100752, +incave2,10000,1000000,310,570,880,0,0,1,1,1,0,0,0,100753, +ioniza,10000,1000000,170,340,700,850,0,1,1,1,1,0,0,100754, +guilty,10000,1000000,150,280,500,0,0,1,1,1,0,0,0,100755, +keraun,10000,1000000,250,520,790,0,0,1,1,1,0,0,0,100756, +landin2,10000,1000000,200,340,590,660,0,1,1,1,1,0,0,100757, +videog,10000,1000000,210,370,620,0,0,1,1,1,0,0,0,100758, +loseyo,10000,1000000,200,300,710,0,0,1,1,1,0,0,0,100759, +machin,10000,1000000,120,280,720,0,0,1,1,1,0,0,0,100760, +makeit,10000,1000000,110,240,480,0,0,1,1,1,0,0,0,100761, +daydre,10000,1000000,190,360,800,0,0,1,1,1,0,0,0,100762, +metron,10000,1000000,200,440,710,0,0,1,1,1,0,0,0,100763, +milkyw,10000,1000000,220,310,600,0,0,1,1,1,0,0,0,100764, +nayuta,10000,1000000,170,370,680,0,0,1,1,1,0,0,0,100766, +nightm,10000,1000000,200,490,730,0,0,1,1,1,0,0,0,100767, +otherw,10000,1000000,230,410,760,0,0,1,1,1,0,0,0,100768, +overth,10000,1000000,330,570,820,0,250,1,1,1,0,-1,0,100769, +uuuuuu,10000,1000000,230,370,740,0,0,1,1,1,0,0,0,100770, +rainin,10000,1000000,160,410,690,0,0,1,1,1,0,0,0,100771, +raisey,10000,1000000,230,550,750,0,150,1,1,1,0,-1,0,100772, +resona,10000,1000000,170,320,640,0,0,1,1,1,0,0,0,100773, +reuniv,10000,1000000,140,230,410,0,0,1,1,1,0,0,0,100774, +rhythm,10000,1000000,370,560,780,0,250,1,1,1,0,-1,0,100775, +rushhh,10000,1000000,250,370,750,0,0,1,1,1,0,0,0,100776, +steeee,10000,1000000,300,580,870,0,0,1,1,1,0,0,0,100777, +sangey,10000,1000000,270,470,850,0,0,1,1,1,0,0,0,100778, +senpai,10000,1000000,380,540,770,0,250,1,1,1,0,-1,0,100779, +sestea,10000,1000000,270,470,760,0,0,1,1,1,0,0,0,100780, +silver,10000,1000000,280,400,690,0,0,1,1,1,0,0,0,100781, +sodama,10000,1000000,200,400,650,0,0,1,1,1,0,0,0,100782, +stardu,10000,1000000,190,330,640,0,0,1,1,1,0,0,0,100783, +starti,10000,1000000,170,310,540,700,0,1,1,1,1,0,0,100784, +sunday,10000,1000000,180,290,460,670,0,1,1,1,1,0,0,100785, +sundro2,10000,1000000,300,480,790,820,0,1,1,1,1,0,0,100786, +sunnyd,10000,1000000,230,380,590,0,0,1,1,1,0,0,0,100787, +superl,10000,1000000,150,320,590,0,0,1,1,1,0,0,0,100788, +switch,10000,1000000,160,350,690,0,0,1,1,1,0,0,0,100789, +theepi2,10000,1000000,220,370,650,0,0,1,1,1,0,0,0,100790, +epipha,10000,1000000,150,300,700,0,0,1,1,1,0,0,0,100791, +thekin,10000,1000000,220,520,750,0,0,1,1,1,0,0,0,100792, +timele,10000,1000000,160,330,720,0,0,1,1,1,0,0,0,100793, +tokyoo,10000,1000000,150,330,710,0,0,1,1,1,0,0,0,100794, +toooma,10000,1000000,300,510,770,0,0,1,1,1,0,0,0,100795, +toucho2,10000,1000000,170,320,520,780,0,1,1,1,1,0,0,100796, +tayuta,10000,1000000,260,350,720,0,0,1,1,1,0,0,0,100797, +ultrix,10000,1000000,270,450,760,0,0,1,1,1,0,0,0,100798, +underw,10000,1000000,290,460,690,860,0,1,1,1,1,0,0,100799, +virtua,10000,1000000,150,350,630,0,0,1,1,1,0,0,0,100800, +voiceo2,10000,1000000,140,380,670,0,0,1,1,1,0,0,0,100801, +wannab2,10000,1000000,260,410,690,0,0,1,1,1,0,0,0,100802, +wiwwtw2,10000,1000000,200,430,670,720,0,1,1,1,1,0,0,100803, +wingso,10000,1000000,200,530,710,0,0,1,1,1,0,0,0,100804, +winter,10000,1000000,140,240,410,0,0,1,1,1,0,0,0,100805, +iineee,10000,1000000,210,400,810,0,0,1,1,1,0,0,0,100806, +illumi,10000,1000000,100,250,460,630,0,1,1,1,1,0,0,100807, +yellll,10000,1000000,80,170,520,0,0,1,1,1,0,0,0,100808, +eschat,10000,1000000,360,570,770,0,250,1,1,1,0,-1,0,100809, +counte,10000,1000000,290,340,710,0,0,1,1,1,0,0,0,100810, +gimcho,10000,1000000,180,390,700,0,0,1,1,1,0,0,0,100811, +surviv,10000,1000000,240,400,650,0,0,1,1,1,0,0,0,100812, +turkis3,10000,1000000,60,200,480,0,0,1,1,1,0,0,0,100814, +picora2,10000,1000000,280,530,800,0,0,1,1,1,0,0,0,100815, +fortis,10000,1000000,200,370,530,0,0,1,1,1,0,0,0,100816, +hedban,10000,1000000,160,430,660,0,0,1,1,1,0,0,0,100817, +megitu,10000,1000000,150,300,490,0,0,1,1,1,0,0,0,100818, +rockma,10000,1000000,270,480,730,0,0,1,1,1,0,0,0,100819, +kounen2,10000,1000000,210,430,730,0,0,1,1,1,0,0,0,100820, +saisyu,10000,1000000,180,360,560,0,0,1,1,1,0,0,0,100821, +yuukan,10000,1000000,220,330,780,0,0,1,1,1,0,0,0,100822, +modern,10000,1000000,200,320,560,0,0,1,1,1,0,0,0,100823, +miraie,10000,1000000,210,350,660,0,0,1,1,1,0,0,0,100824, +ranfes,10000,1000000,200,420,650,0,0,1,1,1,0,0,0,100825, +nemure,10000,1000000,150,380,670,760,0,1,1,1,1,0,0,100826, +yuwaku,10000,1000000,150,260,430,0,0,1,1,1,0,0,0,100827, +dontst,10000,1000000,150,320,560,700,0,1,1,1,1,0,0,100828, +mottai,10000,1000000,100,260,360,0,0,1,1,1,0,0,0,100829, +slysly,10000,1000000,100,330,580,0,0,1,1,1,0,0,0,100830, +lookam,10000,1000000,170,340,670,0,0,1,1,1,0,0,0,100831, +feverr,10000,1000000,280,480,680,0,0,1,1,1,0,0,0,100832, +fashio,10000,1000000,80,240,390,0,0,1,1,1,0,0,0,100833, +hagito,10000,1000000,120,260,500,0,0,1,1,1,0,0,0,100834, +invade,10000,1000000,100,280,470,0,0,1,1,1,0,0,0,100835, +ainoch,10000,1000000,170,400,590,0,0,1,1,1,0,0,0,100836, +nakama,10000,1000000,140,320,530,0,0,1,1,1,0,0,0,100837, +ninjar,10000,1000000,80,230,410,650,0,1,1,1,1,0,0,100838, +parall,10000,1000000,140,350,610,0,0,1,1,1,0,0,0,100839, +yukifu,10000,1000000,130,290,510,0,0,1,1,1,0,0,0,100840, +furiso,10000,1000000,120,240,440,740,0,1,1,1,1,0,0,100841, +honeyj,10000,100000,320,630,880,930,240,1,1,1,1,1,6,100842, +emeraj,10000,100000,300,530,850,0,190,1,1,1,0,1,1,100843, +dazzlo,10000,200000,350,600,800,900,160,1,1,1,1,1,1,100844, diff --git a/titles/cxb/rss1_data/NewsList.csv b/titles/cxb/rss1_data/NewsList.csv new file mode 100644 index 0000000..e95551c --- /dev/null +++ b/titles/cxb/rss1_data/NewsList.csv @@ -0,0 +1,2 @@ +1,1,1601510400,4096483201,1,0,0,0,1,0,news1069,,,1,1, +2,1,1601510400,4096483201,1,0,0,0,1,0,news1070,,,1,1, diff --git a/titles/cxb/rss1_data/Partner0000.csv b/titles/cxb/rss1_data/Partner0000.csv new file mode 100644 index 0000000..973e5a9 --- /dev/null +++ b/titles/cxb/rss1_data/Partner0000.csv @@ -0,0 +1,100 @@ +1,0,100,100,100,100,100,100,100,100,0,0, +2,100,110,101,101,103,101,101,101,101,1000,0, +3,210,120,102,102,106,102,102,102,102,0,0, +4,331,130,103,103,109,103,103,103,103,0,0, +5,464,140,104,104,112,104,104,104,104,1001,0, +6,610,150,105,105,115,105,105,105,105,1002,0, +7,771,160,106,106,118,106,106,106,106,1003,0, +8,948,170,107,107,121,107,107,107,107,0,0, +9,1143,180,108,108,124,108,108,108,108,0,0, +10,1357,190,109,109,127,109,109,109,109,1004,0, +11,1593,200,110,110,130,110,110,110,110,0,0, +12,1853,210,111,111,133,111,111,111,111,0,0, +13,2138,220,112,112,136,112,112,112,112,0,0, +14,2452,230,113,113,139,113,113,113,113,0,0, +15,2797,240,114,114,142,114,114,114,114,1005,0, +16,3177,250,115,115,145,115,115,115,115,0,0, +17,3594,260,116,116,148,116,116,116,116,0,0, +18,4054,270,117,117,151,117,117,117,117,0,0, +19,4559,280,118,118,154,118,118,118,118,0,0, +20,5115,290,119,119,157,119,119,119,119,1006,1, +21,5727,300,120,120,160,120,120,120,120,0,1, +22,6400,310,121,121,163,121,121,121,121,0,1, +23,7140,320,122,122,166,122,122,122,122,0,1, +24,7954,330,123,123,169,123,123,123,123,0,1, +25,8849,340,124,124,172,124,124,124,124,0,1, +26,9834,350,125,125,175,125,125,125,125,0,1, +27,10918,360,126,126,178,126,126,126,126,0,1, +28,12109,370,127,127,181,127,127,127,127,0,1, +29,13420,380,128,128,184,128,128,128,128,0,1, +30,14863,390,129,129,187,129,129,129,129,0,1, +31,16449,400,130,130,190,130,130,130,130,0,1, +32,18194,410,131,131,193,131,131,131,131,0,1, +33,20113,420,132,132,196,132,132,132,132,0,1, +34,22225,430,133,133,199,133,133,133,133,0,1, +35,24547,440,134,134,202,134,134,134,134,0,1, +36,27102,450,135,135,205,135,135,135,135,0,1, +37,29912,460,136,136,208,136,136,136,136,0,1, +38,33003,470,137,137,211,137,137,137,137,0,1, +39,36404,480,138,138,214,138,138,138,138,0,1, +40,40144,490,139,139,217,139,139,139,139,0,1, +41,44259,500,140,140,220,140,140,140,140,0,1, +42,48785,510,141,141,223,141,141,141,141,0,1, +43,53763,520,142,142,226,142,142,142,142,0,1, +44,59240,530,143,143,229,143,143,143,143,0,1, +45,65264,540,144,144,232,144,144,144,144,0,1, +46,71890,550,145,145,235,145,145,145,145,0,1, +47,79179,560,146,146,238,146,146,146,146,0,1, +48,87197,570,147,147,241,147,147,147,147,0,1, +49,96017,580,148,148,244,148,148,148,148,0,1, +50,105718,590,149,149,247,149,149,149,149,0,2, +51,116390,600,150,150,250,150,150,150,150,0,2, +52,128129,610,151,151,253,151,151,151,151,0,2, +53,141042,620,152,152,256,152,152,152,152,0,2, +54,155247,630,153,153,259,153,153,153,153,0,2, +55,170871,640,154,154,262,154,154,154,154,0,2, +56,188059,650,155,155,265,155,155,155,155,0,2, +57,206965,660,156,156,268,156,156,156,156,0,2, +58,227761,670,157,157,271,157,157,157,157,0,2, +59,250637,680,158,158,274,158,158,158,158,0,2, +60,275801,690,159,159,277,159,159,159,159,0,2, +61,303481,700,160,160,280,160,160,160,160,0,2, +62,333929,710,161,161,283,161,161,161,161,0,2, +63,367422,720,162,162,286,162,162,162,162,0,2, +64,404265,730,163,163,289,163,163,163,163,0,2, +65,444791,740,164,164,292,164,164,164,164,0,2, +66,489370,750,165,165,295,165,165,165,165,0,2, +67,538407,760,166,166,298,166,166,166,166,0,2, +68,592348,770,167,167,301,167,167,167,167,0,2, +69,651683,780,168,168,304,168,168,168,168,0,2, +70,716951,790,169,169,307,169,169,169,169,0,2, +71,788746,800,170,170,310,170,170,170,170,0,2, +72,867721,810,171,171,313,171,171,171,171,0,2, +73,954593,820,172,172,316,172,172,172,172,0,2, +74,1050153,830,173,173,319,173,173,173,173,0,2, +75,1155268,840,174,174,322,174,174,174,174,0,2, +76,1270895,850,175,175,325,175,175,175,175,0,2, +77,1398084,860,176,176,328,176,176,176,176,0,2, +78,1537993,870,177,177,331,177,177,177,177,0,2, +79,1691892,880,178,178,334,178,178,178,178,0,2, +80,1861182,890,179,179,337,179,179,179,179,0,2, +81,2047400,900,180,180,340,180,180,180,180,0,2, +82,2252240,910,181,181,343,181,181,181,181,0,2, +83,2477564,920,182,182,346,182,182,182,182,0,2, +84,2725420,930,183,183,349,183,183,183,183,0,2, +85,2998062,940,184,184,352,184,184,184,184,0,2, +86,3297969,950,185,185,355,185,185,185,185,0,2, +87,3627865,960,186,186,358,186,186,186,186,0,2, +88,3990752,970,187,187,361,187,187,187,187,0,2, +89,4389927,980,188,188,364,188,188,188,188,0,2, +90,4829020,990,189,189,367,189,189,189,189,0,2, +91,5312022,1000,190,190,370,190,190,190,190,0,2, +92,5843324,1010,191,191,373,191,191,191,191,0,2, +93,6427757,1020,192,192,376,192,192,192,192,0,2, +94,7070633,1030,193,193,379,193,193,193,193,0,2, +95,7777796,1040,194,194,382,194,194,194,194,0,2, +96,8555676,1050,195,195,385,195,195,195,195,0,2, +97,9411343,1060,196,196,388,196,196,196,196,0,2, +98,10352578,1070,197,197,391,197,197,197,197,0,2, +99,11387935,1080,198,198,394,198,198,198,198,0,2, +100,12526829,1090,199,199,397,199,199,199,199,0,2, diff --git a/titles/cxb/rss1_data/Shop/ShopList_Icon.csv b/titles/cxb/rss1_data/Shop/ShopList_Icon.csv new file mode 100644 index 0000000..673ad1b --- /dev/null +++ b/titles/cxb/rss1_data/Shop/ShopList_Icon.csv @@ -0,0 +1,3 @@ +1000,1,10000,1,-1,-,1411697520,1670178694,ic0000,10,1,,1,先行解ç¦,1,-, +1001,2,10000,1,-1,-,1411697520,1670178694,ic0001,10,1,,1,先行解ç¦,1,-, +1002,3,10000,1,-1,-,1412103600,1670178694,ic0002,10,2,,1,先行解ç¦,1,-, \ No newline at end of file diff --git a/titles/cxb/rss1_data/Shop/ShopList_Music.csv b/titles/cxb/rss1_data/Shop/ShopList_Music.csv new file mode 100644 index 0000000..0e672c2 --- /dev/null +++ b/titles/cxb/rss1_data/Shop/ShopList_Music.csv @@ -0,0 +1,13 @@ +shopID.,®——pNo.,Ver.,oŒ»ƒtƒ‰ƒO,oŒ»ƒtƒ‰ƒOŽQÆID,ðŒ,oŒ»ŽžŠÔ,Á–ÅŽžŠÔ,MusicCode,‰¿Ši,•\Ž¦ƒ^ƒCƒv,Text,Type,Value(op),Value,‘ÎÛ‹È,Difficulty(op),Difficulty,Level(op),Level,Grade(Op),Grade,GaugeType(op),GaugeType,HS(op),HS,APP,DAP,F-V,F-H,FullCombo,Combo(op),Combo,ClearRate(op),ClearRate,ƒvƒŒƒC“ú,’nˆæ, +0,1,1.00.00,3,1,-,1411697520.0288,1443233520.0288,megaro,0,0,-,-1,-1,-1,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +1,2,1.00.00,1,-1,-,1411697520.0288,1443233520.0288,nature,10,2,???,-1,-1,-1,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +2,3,1.00.00,2,-1,-,1412103600.0288,1443639598.992,hopesb,30,1,uLanding on the moonv‚ðSTANDARDˆÈã‚ŃNƒŠƒA‚·‚éB,0,-1,-1,landin,1,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +3,4,1.00.00,1,-1,-,1412103600.0288,1443639598.992,flower,10,0,-,-1,-1,-1,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +4,5,1.00.02,1,-1,1&2&3,1412103600.0288,1443639598.992,reseed3,10,0,uHuman NaturevuHopes BrightvuFlowerwallv‚ðƒNƒŠƒA‚·‚éB,-1,-1,-1,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +5,6,1.00.00,1,-1,-,1411697520.0288,1443233520.0288,dennou,20,1,-,0,-1,-1,flower,1,2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,2,-1,-1,-1,-1,,, +6,7,1.00.00,2,-1,5&7,1411697520.0288,1443233520.0288,romanc,10,1,-,-1,-1,-1,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +7,8,1.00.00,1,-1,-,1411697520.0288,1443233520.0288,landin,40,1,-,-1,-1,-1,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +8,9,1.00.00,1,-1,7,1411697520.0288,1443233520.0288,ididid,50,0,-,-1,-1,-1,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,2,-1,-1,-1,-1,,, +9,10,1.00.00,1,-1,-,1411697520.0288,1443233520.0288,crissc,60,0,MASTERˆÈã‚Ì2‹ÈS+ˆÈãƒtƒ‹ƒRƒ“ƒ{ƒNƒŠƒA‚·‚éB,1,1,2,-,1,2,-1,0,1,1,1,2,-1,-1,-1,-1,-1,-1,1,-1,-1,-1,-1,,, +10,11,1.00.00,1,-1,-,1411697520.0288,1443233520.0288,dazzli,70,1,MASTERˆÈã‚Ì4‹ÈS+ˆÈãƒNƒŠƒA‚·‚éB,1,1,4,-,1,2,-1,0,1,1,1,2,-1,-1,-1,-1,-1,-1,2,-1,-1,-1,-1,,, +11,12,1.00.00,1,-1,-,1411697520.0288,1443233520.0288,izimed,987654,1,MASTERˆÈã‚Ì7‹ÈS+ˆÈãƒNƒŠƒA‚·‚éB,1,1,7,-,1,2,-1,0,1,1,1,2,-1,-1,-1,-1,-1,-1,2,-1,-1,-1,-1,,, diff --git a/titles/cxb/rss1_data/Shop/ShopList_Sale.csv b/titles/cxb/rss1_data/Shop/ShopList_Sale.csv new file mode 100644 index 0000000..5e9fb72 --- /dev/null +++ b/titles/cxb/rss1_data/Shop/ShopList_Sale.csv @@ -0,0 +1,3 @@ +saleID.,�J�n��,�I����,ShopID,Price, +0,1411696799,1443236400,0,7000, +1,1411783199,1443322800,1,7000, diff --git a/titles/cxb/rss1_data/Shop/ShopList_SkinBg.csv b/titles/cxb/rss1_data/Shop/ShopList_SkinBg.csv new file mode 100644 index 0000000..44c4843 --- /dev/null +++ b/titles/cxb/rss1_data/Shop/ShopList_SkinBg.csv @@ -0,0 +1,4 @@ +shopID.,®——pNo.,Ver.,oŒ»ƒtƒ‰ƒO,oŒ»ƒtƒ‰ƒOŽQÆID,ðŒ,oŒ»ŽžŠÔ,Á–ÅŽžŠÔ,ItemCode,‰¿Ši,•\Ž¦ƒ^ƒCƒv,Text,Type,Value(op),Value,‘ÎÛ‹È,Difficulty(op),Difficulty,Level(op),Level,Grade(Op),Grade,GaugeType(op),GaugeType,HS(op)i,HS,APP,DAP,F-V,F-H,FullCombo,Combo(op),Combo,ClearRate(op),ClearRate,ƒvƒŒƒC“ú,’nˆæ, +3000,1,1.00.00,1,-1,-,1411697520.0288,1443233520.0288,skb0000,10,1,MASTERˆÈã‚Ì2‹ÈS+ˆÈãƒtƒ‹ƒRƒ“ƒ{ƒNƒŠƒA‚·‚éB,1,1,1,-,1,2,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +3001,2,1.00.00,1,-1,-,1411697520.0288,1443233520.0288,skb0001,10,1,Next Frontier (Masterj‚ðƒNƒŠƒA,0,-1,-1,bleeze,1,2,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +3002,3,1.00.00,1,-1,-,1412103600.0288,1443639598.992,skb0002,10,2,MasterˆÈã‚ð1‹È‚ðS+ˆÈã‚ŃNƒŠƒA‚·‚éB,1,1,1,-,1,2,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,1, diff --git a/titles/cxb/rss1_data/Shop/ShopList_SkinEffect.csv b/titles/cxb/rss1_data/Shop/ShopList_SkinEffect.csv new file mode 100644 index 0000000..bb6486e --- /dev/null +++ b/titles/cxb/rss1_data/Shop/ShopList_SkinEffect.csv @@ -0,0 +1,11 @@ +shopID.,�����pNo.,Ver.,�o���t���O,�o���t���O�Q��ID,����,�o������,���Ŏ���,ItemCode,���i,�\���^�C�v,Text,Type,Value(op),Value,�Îۋ�,Difficulty(op),Difficulty,Level(op),Level,Grade(Op),Grade,GaugeType(op),GaugeType,HS(op)i,HS,APP,DAP,F-V,F-H,FullCombo,Combo(op),Combo,ClearRate(op),ClearRate,�v���C��,�n��, +5000,1,10000,1,-1,-,1411697520,1443233520,ske0000,10,1,MASTER�È��2��S+�È�t���R���{�N���A����B,1,1,1,-,1,2,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +5001,2,10000,1,-1,-,1411697520,1443233520,ske0001,10,1,Next Frontier (Master�j���N���A,0,-1,-1,megaro,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +5002,3,10000,1,-1,-,1412103600,1443639598,ske0002,10,2,Master�È��1�Ȃ�S+�È�ŃN���A����B,1,1,1,-,1,2,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,1, +5003,4,10000,1,-1,-,1412103600,1443639598,ske0003,10,0,Master�È��1�Ȃ�S+�È�ŃN���A����B,1,1,1,-,1,2,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +5004,5,10000,1,-1,-,1412103600,1443639598,ske0004,10,2,2�ȃN���A,1,1,2,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +5005,5,10000,1,-1,-,1412103600,1443639598,ske0005,10,2,3�ȃN���A,1,1,3,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +5006,5,10000,1,-1,-,1412103600,1443639598,ske0006,10,2,4�ȃN���A,1,1,4,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +5007,5,10000,1,-1,-,1412103600,1443639598,ske0007,10,2,5�ȃN���A,1,1,5,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +5008,5,10000,1,-1,-,1412103600,1443639598,ske0008,10,2,6�ȃN���A,1,1,6,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +5009,5,10000,1,-1,-,1412103600,1443639598,ske0009,10,2,7�ȃN���A,1,1,7,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, diff --git a/titles/cxb/rss1_data/Shop/ShopList_SkinNotes.csv b/titles/cxb/rss1_data/Shop/ShopList_SkinNotes.csv new file mode 100644 index 0000000..0b496f2 --- /dev/null +++ b/titles/cxb/rss1_data/Shop/ShopList_SkinNotes.csv @@ -0,0 +1,6 @@ +shopID.,�����pNo.,Ver.,�o���t���O,�o���t���O�Q��ID,����,�o������,���Ŏ���,ItemCode,���i,�\���^�C�v,Text,Type,Value(op),Value,�Îۋ�,Difficulty(op),Difficulty,Level(op),Level,Grade(Op),Grade,GaugeType(op),GaugeType,HS(op)i,HS,APP,DAP,F-V,F-H,FullCombo,Combo(op),Combo,ClearRate(op),ClearRate,�v���C��,�n��, +4000,1,10000,1,-1,-,1411697520,4096483201,skt0000,10,1,MASTER�È��2��S+�È�t���R���{�N���A����B,1,1,1,-,1,2,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +4001,2,10000,1,-1,-,1411697520,4096483201,skt0001,10,1,Next Frontier (Master�j���N���A,0,-1,-1,megaro,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +4002,3,10000,1,-1,-,1412103600,4096483201,skt0002,10,2,Master�È��1�Ȃ�S+�È�ŃN���A����B,1,1,1,-,1,2,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,1, +4003,4,10000,1,-1,-,1412103600,4096483201,skt0003,10,0,Master�È��1�Ȃ�S+�È�ŃN���A����B,1,1,1,-,1,2,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +4004,5,10000,1,-1,-,1412103600,4096483201,skt0004,10,2,aaaaaaaaaaaaaaaaa,1,1,20,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, diff --git a/titles/cxb/rss1_data/Shop/ShopList_Title.csv b/titles/cxb/rss1_data/Shop/ShopList_Title.csv new file mode 100644 index 0000000..24d4cd9 --- /dev/null +++ b/titles/cxb/rss1_data/Shop/ShopList_Title.csv @@ -0,0 +1,3 @@ +2000,1001,10000,1,-1,-,1411697520,4096483201,,10,1,,1,,1,先行解ç¦, +2001,1002,10000,1,-1,-,1411697520,4096483201,,10,1,,1,,1,先行解ç¦, +2002,1003,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,先行解ç¦, diff --git a/titles/cxb/rss2.py b/titles/cxb/rss2.py new file mode 100644 index 0000000..5ae98f4 --- /dev/null +++ b/titles/cxb/rss2.py @@ -0,0 +1,262 @@ +import json +from decimal import Decimal +from base64 import b64encode +from typing import Any, Dict +from hashlib import md5 +from datetime import datetime + +from core.config import CoreConfig +from core.data import Data, cached +from titles.cxb.config import CxbConfig +from titles.cxb.base import CxbBase +from titles.cxb.const import CxbConstants + +class CxbRevSunriseS2(CxbBase): + def __init__(self, cfg: CoreConfig, game_cfg: CxbConfig) -> None: + super().__init__(cfg, game_cfg) + self.version = CxbConstants.VER_CROSSBEATS_REV_SUNRISE_S2_OMNI + + def handle_data_path_list_request(self, data: Dict) -> Dict: + return { "data": "" } + + @cached(lifetime=86400) + def handle_data_music_list_request(self, data: Dict) -> Dict: + ret_str = "" + with open(r"titles/cxb/rss2_data/MusicArchiveList.csv") as music: + lines = music.readlines() + for line in lines: + line_split = line.split(',') + ret_str += f"{line_split[0]},{line_split[1]},{line_split[2]},{line_split[3]},{line_split[4]},{line_split[5]},{line_split[6]},{line_split[7]},{line_split[8]},{line_split[9]},{line_split[10]},{line_split[11]},{line_split[12]},{line_split[13]},{line_split[14]},\r\n" + + return({"data":ret_str}) + + @cached(lifetime=86400) + def handle_data_item_list_detail_request(self, data: Dict) -> Dict: + #ItemListIcon load + ret_str = "#ItemListIcon\r\n" + with open(r"titles/cxb/rss2_data/Item/ItemList_Icon.csv", encoding="utf-8") as item: + lines = item.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + + #ItemListTitle load + ret_str += "\r\n#ItemListTitle\r\n" + with open(r"titles/cxb/rss2_data/Item/ItemList_Title.csv", encoding="utf-8") as item: + lines = item.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + + return({"data":ret_str}) + + @cached(lifetime=86400) + def handle_data_shop_list_detail_request(self, data: Dict) -> Dict: + #ShopListIcon load + ret_str = "#ShopListIcon\r\n" + with open(r"titles/cxb/rss2_data/Shop/ShopList_Icon.csv", encoding="utf-8") as shop: + lines = shop.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + + #ShopListMusic load + ret_str += "\r\n#ShopListMusic\r\n" + with open(r"titles/cxb/rss2_data/Shop/ShopList_Music.csv", encoding="utf-8") as shop: + lines = shop.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + + #ShopListSale load + ret_str += "\r\n#ShopListSale\r\n" + with open(r"titles/cxb/rss2_data/Shop/ShopList_Sale.csv", encoding="shift-jis") as shop: + lines = shop.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + + #ShopListSkinBg load + ret_str += "\r\n#ShopListSkinBg\r\n" + with open(r"titles/cxb/rss2_data/Shop/ShopList_SkinBg.csv", encoding="shift-jis") as shop: + lines = shop.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + + #ShopListSkinEffect load + ret_str += "\r\n#ShopListSkinEffect\r\n" + with open(r"titles/cxb/rss2_data/Shop/ShopList_SkinEffect.csv", encoding="shift-jis") as shop: + lines = shop.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + + #ShopListSkinNotes load + ret_str += "\r\n#ShopListSkinNotes\r\n" + with open(r"titles/cxb/rss2_data/Shop/ShopList_SkinNotes.csv", encoding="shift-jis") as shop: + lines = shop.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + + #ShopListTitle load + ret_str += "\r\n#ShopListTitle\r\n" + with open(r"titles/cxb/rss2_data/Shop/ShopList_Title.csv", encoding="utf-8") as shop: + lines = shop.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + return({"data":ret_str}) + + def handle_data_extra_stage_list_request(self, data: Dict) -> Dict: + return({"data":""}) + + def handle_data_ex0001_request(self, data: Dict) -> Dict: + return({"data":""}) + + def handle_data_one_more_extra_list_request(self, data: Dict) -> Dict: + return({"data":""}) + + def handle_data_bonus_list10100_request(self, data: Dict) -> Dict: + return({"data":""}) + + def handle_data_oe0001_request(self, data: Dict) -> Dict: + return({"data":""}) + + def handle_data_free_coupon_request(self, data: Dict) -> Dict: + return({"data":""}) + + @cached(lifetime=86400) + def handle_data_news_list_request(self, data: Dict) -> Dict: + ret_str = "" + with open(r"titles/cxb/rss2_data/NewsList.csv", encoding="UTF-8") as news: + lines = news.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + return({"data":ret_str}) + + def handle_data_tips_request(self, data: Dict) -> Dict: + return({"data":""}) + + def handle_data_release_info_list_request(self, data: Dict) -> Dict: + return({"data":""}) + + @cached(lifetime=86400) + def handle_data_random_music_list_request(self, data: Dict) -> Dict: + ret_str = "" + with open(r"titles/cxb/rss2_data/MusicArchiveList.csv") as music: + lines = music.readlines() + count = 0 + for line in lines: + line_split = line.split(",") + ret_str += str(count) + "," + line_split[0] + "," + line_split[0] + ",\r\n" + + return({"data":ret_str}) + + @cached(lifetime=86400) + def handle_data_license_request(self, data: Dict) -> Dict: + ret_str = "" + with open(r"titles/cxb/rss2_data/License.csv", encoding="UTF-8") as licenses: + lines = licenses.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + return({"data":ret_str}) + + @cached(lifetime=86400) + def handle_data_course_list_request(self, data: Dict) -> Dict: + ret_str = "" + with open(r"titles/cxb/rss2_data/Course/CourseList.csv", encoding="UTF-8") as course: + lines = course.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + return({"data":ret_str}) + + @cached(lifetime=86400) + def handle_data_csxxxx_request(self, data: Dict) -> Dict: + extra_num = int(data["dldate"]["filetype"][-4:]) + ret_str = "" + with open(fr"titles/cxb/rss2_data/Course/Cs{extra_num}.csv", encoding="shift-jis") as course: + lines = course.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + return({"data":ret_str}) + + def handle_data_mission_list_request(self, data: Dict) -> Dict: + return({"data":""}) + + def handle_data_mission_bonus_request(self, data: Dict) -> Dict: + return({"data":""}) + + def handle_data_unlimited_mission_request(self, data: Dict) -> Dict: + return({"data":""}) + + def handle_data_partner_list_request(self, data: Dict) -> Dict: + ret_str = "" + # Lord forgive me for the sins I am about to commit + for i in range(0,10): + ret_str += f"80000{i},{i},{i},0,10000,,\r\n" + ret_str += f"80000{i},{i},{i},1,10500,,\r\n" + ret_str += f"80000{i},{i},{i},2,10500,,\r\n" + for i in range(10,13): + ret_str += f"8000{i},{i},{i},0,10000,,\r\n" + ret_str += f"8000{i},{i},{i},1,10500,,\r\n" + ret_str += f"8000{i},{i},{i},2,10500,,\r\n" + ret_str +="\r\n---\r\n0,150,100,100,100,100,\r\n" + for i in range(1,130): + ret_str +=f"{i},100,100,100,100,100,\r\n" + + ret_str += "---\r\n" + return({"data": ret_str}) + + @cached(lifetime=86400) + def handle_data_partnerxxxx_request(self, data: Dict) -> Dict: + partner_num = int(data["dldate"]["filetype"][-4:]) + ret_str = f"{partner_num},,{partner_num},1,10000,\r\n" + with open(r"titles/cxb/rss2_data/Partner0000.csv") as partner: + lines = partner.readlines() + for line in lines: + ret_str += f"{line[:-1]}\r\n" + return({"data": ret_str}) + + def handle_data_server_state_request(self, data: Dict) -> Dict: + return({"data": True}) + + def handle_data_settings_request(self, data: Dict) -> Dict: + return({"data": "2,\r\n"}) + + def handle_data_story_list_request(self, data: Dict) -> Dict: + #story id, story name, game version, start time, end time, course arc, unlock flag, song mcode for menu + ret_str = "\r\n" + ret_str += f"st0000,RISING PURPLE,10104,1464370990,4096483201,Cs1000,-1,purple,\r\n" + ret_str += f"st0001,REBEL YELL,10104,1467999790,4096483201,Cs1000,-1,chaset,\r\n" + ret_str += f"st0002,REMNANT,10104,1502127790,4096483201,Cs1000,-1,overcl,\r\n" + return({"data": ret_str}) + + def handle_data_stxxxx_request(self, data: Dict) -> Dict: + story_num = int(data["dldate"]["filetype"][-4:]) + ret_str = "" + # Each stories appears to have 10 pieces based on the wiki but as on how they are set.... no clue + for i in range(1,11): + ret_str +=f"{i},st000{story_num}_{i-1},,,,,,,,,,,,,,,,1,,-1,1,\r\n" + return({"data": ret_str}) + + def handle_data_event_stamp_list_request(self, data: Dict) -> Dict: + return({"data":"Cs1002,1,1,1,1,1,1,1,1,1,1,\r\n"}) + + def handle_data_premium_list_request(self, data: Dict) -> Dict: + return({"data": "1,,,,10,,,,,99,,,,,,,,,100,,\r\n"}) + + def handle_data_event_list_request(self, data: Dict) -> Dict: + return({"data":"Cs4001,0,10000,1601510400,1604188799,1,nv2006,1,\r\nCs4005,0,10000,1609459200,1615766399,1,nv2006,1,\r\n"}) + + def handle_data_event_detail_list_request(self, data: Dict) -> Dict: + event_id = data["dldate"]["filetype"].split("/")[2] + if "Cs4001" in event_id: + return({"data":"#EventMusicList\r\n1,zonzon2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,\r\n2,moonki,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,\r\n3,tricko,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,\r\n"}) + elif "Cs4005" in event_id: + return({"data":"#EventMusicList\r\n2,firstl,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,\r\n2,valent,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,\r\n2,dazzli2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,\r\n"}) + elif "EventStampMapListCs1002" in event_id: + return({"data":"1,2,1,1,2,3,9,5,6,7,8,9,10,\r\n"}) + elif "EventStampList" in event_id: + return({"data":"Cs1002,1,1,1,1,1,1,1,1,1,1,\r\n"}) + else: + return({"data":""}) + + def handle_data_event_stamp_map_list_csxxxx_request(self, data: Dict) -> Dict: + event_id = data["dldate"]["filetype"].split("/")[2] + if "EventStampMapListCs1002" in event_id: + return({"data":"1,2,1,1,2,3,9,5,6,7,8,9,10,\r\n"}) + else: + return({"data":""}) diff --git a/titles/cxb/rss2_data/Course/CourseList.csv b/titles/cxb/rss2_data/Course/CourseList.csv new file mode 100644 index 0000000..41225e2 --- /dev/null +++ b/titles/cxb/rss2_data/Course/CourseList.csv @@ -0,0 +1,5 @@ +Cs2000,0,10050,1422284400,4096483201,0,-1,-1,100,5,クリアã™ã‚‹ã¨æ®µä½ãŒã€Œè¦‹ç¿’ã„ã€ã«æ˜‡æ ¼ã—ã¾ã™ã€‚,-1, +Cs2001,1,10050,1422284400,4096483201,0,-1,-1,110,5,クリアã™ã‚‹ã¨æ®µä½ãŒã€Œåˆæ®µã€ã«æ˜‡æ ¼ã—ã¾ã™ã€‚,-1, +Cs2002,2,10050,1422284400,4096483201,0,-1,-1,120,5,クリアã™ã‚‹ã¨æ®µä½ãŒã€ŒäºŒæ®µã€ã«æ˜‡æ ¼ã—ã¾ã™ã€‚,-1, +Cs2003,3,10050,1422284400,4096483201,0,-1,-1,130,5,クリアã™ã‚‹ã¨æ®µä½ãŒã€Œä¸‰æ®µã€ã«æ˜‡æ ¼ã—ã¾ã™ã€‚,-1, +Cs2004,4,10050,1422284400,4096483201,0,-1,-1,140,5,クリアã™ã‚‹ã¨æ®µä½ãŒã€Œå››æ®µã€ã«æ˜‡æ ¼ã—ã¾ã™ã€‚,-1, diff --git a/titles/cxb/rss2_data/Course/Cs2000.csv b/titles/cxb/rss2_data/Course/Cs2000.csv new file mode 100644 index 0000000..38a3f5f --- /dev/null +++ b/titles/cxb/rss2_data/Course/Cs2000.csv @@ -0,0 +1,4 @@ +1,flower,0,1,-,-1,-1,2,-1,1,-1,1, +2,bleeze,0,1,-,-1,-1,-1,-1,-1,-1,-1, +3,killyk,0,-1,-,-1,-1,-1,-1,-1,-1,-1, +4,shooti,0,-1,-,-1,-1,-1,-1,-1,-1,-1, diff --git a/titles/cxb/rss2_data/Course/Cs2001.csv b/titles/cxb/rss2_data/Course/Cs2001.csv new file mode 100644 index 0000000..94d4a43 --- /dev/null +++ b/titles/cxb/rss2_data/Course/Cs2001.csv @@ -0,0 +1,4 @@ +1,getthe,0,1,-,-1,-1,2,-1,1,-1,1, +2,picora,0,1,-,-1,-1,-1,-1,-1,-1,-1, +3,nature,1,-1,-,-1,-1,-1,-1,-1,-1,-1, +4,roadof,1,-1,-,-1,-1,-1,-1,-1,-1,-1, diff --git a/titles/cxb/rss2_data/Course/Cs2002.csv b/titles/cxb/rss2_data/Course/Cs2002.csv new file mode 100644 index 0000000..0820970 --- /dev/null +++ b/titles/cxb/rss2_data/Course/Cs2002.csv @@ -0,0 +1,4 @@ +1,landin,1,1,-,-1,-1,2,-1,1,-1,1, +2,powerr,1,1,-,-1,-1,-1,-1,-1,-1,-1, +3,thesig,1,-1,-,-1,-1,-1,-1,-1,-1,-1, +4,bluede,2,-1,-,-1,-1,-1,-1,-1,-1,-1, diff --git a/titles/cxb/rss2_data/Course/Cs2003.csv b/titles/cxb/rss2_data/Course/Cs2003.csv new file mode 100644 index 0000000..540a574 --- /dev/null +++ b/titles/cxb/rss2_data/Course/Cs2003.csv @@ -0,0 +1,4 @@ +1,hosita,2,1,-,-1,-1,2,-1,1,-1,1, +2,techno,2,1,-,-1,-1,-1,-1,-1,-1,-1, +3,moonli,2,-1,-,-1,-1,-1,-1,-1,-1,-1, +4,kounen,2,-1,-,-1,-1,-1,-1,-1,-1,-1, diff --git a/titles/cxb/rss2_data/Course/Cs2004.csv b/titles/cxb/rss2_data/Course/Cs2004.csv new file mode 100644 index 0000000..292133e --- /dev/null +++ b/titles/cxb/rss2_data/Course/Cs2004.csv @@ -0,0 +1,4 @@ +1,planet,2,1,-,-1,-1,2,-1,1,-1,1, +2,firefo,2,1,-,-1,-1,-1,-1,-1,-1,-1, +3,essenc,2,-1,-,-1,-1,-1,-1,-1,-1,-1, +4,sateli,2,-1,-,-1,-1,-1,-1,-1,-1,-1, \ No newline at end of file diff --git a/titles/cxb/rss2_data/Ex0000.csv b/titles/cxb/rss2_data/Ex0000.csv new file mode 100644 index 0000000..8658453 --- /dev/null +++ b/titles/cxb/rss2_data/Ex0000.csv @@ -0,0 +1,10 @@ +StageMax,ClearCount,StageNo.,MCODE,Diff(std),Diff(hrd),Diff(mas),Diff(ulm),Diff(esy),Level(op), Level(val), Grade(op),Grade(val),GT(nml),GT(svl),GT(ult),GT(nhp),GT(esy),HS(op),HS(val),APP,DAP,F-V,F-H,FT(ac),FT(tab),FT(pho),Combo(op),Combo(val),FullCombo,ClearRate(op),ClearRate(val) +2,1,1,-,2,2,2,1,2,-1,-,1,1,-1,-1,-1,-1,-1,1,-2,-1,-1,-1,-1,-1,-1,-1,1,300,-1,-1,-, +2,-,2,-,2,2,2,1,2,-1,-,1,1,-1,-1,-1,-1,-1,1,-2,-1,-1,-1,-1,-1,-1,-1,1,300,-1,-1,-, +3,3,1,-,2,2,1,1,2,-1,-,1,2,-1,-1,-1,-1,-1,1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-,-1,-1,-, +3,-,2,-,2,2,1,1,2,-1,-,1,2,-1,-1,-1,-1,-1,1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-,-1,-1,-, +3,-,3,-,2,2,1,1,2,1,50,1,2,2,2,1,2,2,1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-,-1,-1,-, +4,4,1,dynami:fronti:rebell:fronti2:auflcb:auflcb2,2,2,1,1,2,-1,-,1,-1,-1,-1,-1,-1,-1,1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-,-1,-1,-, +4,-,2,-,2,2,1,1,2,-1,-,1,-1,-1,-1,-1,-1,-1,1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-,-1,-1,-, +4,-,3,-,2,2,1,1,2,-1,-,1,-1,-1,-1,-1,-1,-1,1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-,-1,-1,-, +4,-,4,-,2,2,1,1,2,-1,-,1,-1,-1,-1,-1,-1,-1,1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-,-1,-1,-, diff --git a/titles/cxb/rss2_data/ExtraStageList.csv b/titles/cxb/rss2_data/ExtraStageList.csv new file mode 100644 index 0000000..44f252c --- /dev/null +++ b/titles/cxb/rss2_data/ExtraStageList.csv @@ -0,0 +1,2 @@ +No.,Ver.,ESID,Title,StartTime,EndTime,�o����MCODE,�o��RP,�o���G�t�F�N�g,Diff(std),Diff(hrd),Diff(mas),Diff(ulm),Diff(esy),GT(nml),GT(svl),GT(ult),GT(nhp),GT(esy),HS(op),HS(val),APP,DAP,F-V,F-H,FT(ac),FT(tab),FT(pho) +0,10000,Ex0001,MEGALOMAN[i]A�o��,1411697520.0288,1843233520.0288,megaro,0,3,2,2,1,2,2,2,2,1,2,2,1,-2,-1,-1,-1,-1,-1,-1,-1, \ No newline at end of file diff --git a/titles/cxb/rss2_data/Item/ItemList_Icon.csv b/titles/cxb/rss2_data/Item/ItemList_Icon.csv new file mode 100644 index 0000000..19cbe0c --- /dev/null +++ b/titles/cxb/rss2_data/Item/ItemList_Icon.csv @@ -0,0 +1,162 @@ +1000,ic0000,crossbeats.REV, +1001,ic1718,crossbeats.REV SUNRiSE, +1002,ic0001,曉月 (アカツキ), +1289,ic1005,Phase:01 RISING PURPLE1, +1290,ic1006,Phase:01 RISING PURPLE2, +1291,ic1007,Phase:01 RISING PURPLE3, +1292,ic1008,ANIMAX MUSIX特集, +1293,ic1010,ANIMAX MUSIX特集 第2å¼¾ II, +1294,ic1011,ANIMAX MUSIX特集 第2å¼¾ III, +1295,ic1012,ANIMAX MUSIX特集 第2å¼¾ I, +1296,ic1013,モンスターãƒãƒ³ã‚¿ãƒ¼ã‚¯ãƒ­ã‚¹ç‰¹é›† I, +1297,ic1014,モンスターãƒãƒ³ã‚¿ãƒ¼ã‚¯ãƒ­ã‚¹ç‰¹é›† II, +1298,ic1015,モンスターãƒãƒ³ã‚¿ãƒ¼ã‚¯ãƒ­ã‚¹ç‰¹é›† III, +1299,ic1016,Phase:02 REBEL YELL1, +1300,ic1017,Phase:02 REBEL YELL2, +1301,ic1018,Phase:02 REBEL YELL3, +1302,ic1020,高知イベントå‚加, +1303,ic1021,幕張イベントå‚加, +1304,ic1022,ストリートファイター特集 I, +1305,ic1023,ストリートファイター特集 II, +1306,ic1024,ストリートファイター特集 III, +1307,ic1025,å‰ç¥¥å¯ºãƒ­ã‚±ãƒ†ã‚¹ãƒˆå‚加, +1308,ic1026,Phase:02 REBEL YELL4, +1309,ic1027,新堂敦士コラボイベント I, +1310,ic1028,新堂敦士コラボイベント II, +1311,ic1029,新堂敦士コラボイベント III, +1312,ic1030,第1回クラスãƒãƒ£ãƒ¬ãƒ³ã‚¸ã‚¤ãƒ™ãƒ³ãƒˆ 金賞, +1313,ic1031,第1回クラスãƒãƒ£ãƒ¬ãƒ³ã‚¸ã‚¤ãƒ™ãƒ³ãƒˆ 銀賞, +1314,ic1032,第1回クラスãƒãƒ£ãƒ¬ãƒ³ã‚¸ã‚¤ãƒ™ãƒ³ãƒˆ 銅賞, +1315,ic1033,Phase:03 REMNANT1, +1316,ic1034,Phase:03 REMNANT2, +1317,ic1035,Phase:03 REMNANT3, +1318,ic1036,1st Anniversary, +1319,ic1037,WIXOSSコラボイベント I, +1320,ic1038,WIXOSSコラボイベント II, +1321,ic1039,WIXOSSコラボイベント III, +1322,ic1040,クローニャãŒã‚„ã£ã¦ããŸâ˜†, +1323,ic1041,グルーヴコースター3リンクフィーãƒãƒ¼ã‚³ãƒ©ãƒœã‚¤ãƒ™ãƒ³ãƒˆ I, +1324,ic1042,グルーヴコースター3リンクフィーãƒãƒ¼ã‚³ãƒ©ãƒœã‚¤ãƒ™ãƒ³ãƒˆ II, +1325,ic1043,グルーヴコースター3リンクフィーãƒãƒ¼ã‚³ãƒ©ãƒœã‚¤ãƒ™ãƒ³ãƒˆ III, +1326,ic1044,エスピナス, +1327,ic1045,ディスフィロア, +1328,ic1046,UNKNOWN(覇種), +1329,ic1047,14th Clock(INNOCENT NOIZE特集), +1330,ic1048,Hervor(INNOCENT NOIZE特集), +1331,ic1049,Hervor(INNOCENT NOIZE特集), +1332,ic1050,æ±æ–¹Project楽曲追加記念 霊夢, +1333,ic1051,æ±æ–¹Project楽曲追加記念 é­”ç†æ²™, +1334,ic1052,æ±æ–¹Project楽曲追加記念 ãƒãƒ«ãƒŽ, +1335,ic1053,æ±æ–¹Project楽曲追加記念 アリス, +1336,ic1054,æ±æ–¹Project楽曲追加記念 ç‡, +1337,ic1055,回雪! 氷上ã®ç™½å§«â™¡ç‡¦, +1338,ic1056,復活祭2017~ãƒãƒƒãƒ”ー☆イースター~, +1339,ic1057,Phase:04 ROAR 1, +1340,ic1058,Phase:04 ROAR 2, +1341,ic1059,Phase:04 ROAR 3, +1342,ic1060,カプコンã§ã‚ãã¼ã†! クローニャ, +1343,ic1061,カプコンã§ã‚ãã¼ã†! æšæœˆ, +1344,ic1062,カプコンã§ã‚ãã¼ã†! 燦, +1345,ic1063,SUNRiSE ãŠã‹ã’ã•ã¾ã§1周年☆, +1346,ic1064,ニャるã»ã©..., +1347,ic1065,ã“ã“ã»ã‚Œãƒ‹ãƒ£ãƒ³ãƒ‹ãƒ£ãƒ³! 人間å’業, +1348,ic1066,æ±æ–¹Project楽曲追加記念 レミリア, +1349,ic1067,æ±æ–¹Project楽曲追加記念 咲夜, +1350,ic1068,æ±æ–¹Project楽曲追加記念 赤蛮奇, +1351,ic1069,æ±æ–¹Project楽曲追加記念 ã“ã„ã—, +1352,ic1070,æ±æ–¹Project楽曲追加記念 クラウンピース, +1353,ic1071,Hallowe'en ã‚ã‹ã¤ã, +1354,ic1072,Hallowe'en ã•ã‚“, +1355,ic1073,クリスマスãŒã‚„ã£ã¦ããŸã☆2017 æšæœˆ, +1356,ic1500,北海é“, +1357,ic1501,é’森県, +1358,ic1502,岩手県, +1359,ic1503,宮城県, +1360,ic1504,秋田県, +1361,ic1505,山形県, +1362,ic1506,ç¦å³¶çœŒ, +1363,ic1507,茨城県, +1364,ic1508,栃木県, +1365,ic1509,群馬県, +1366,ic1510,埼玉県, +1367,ic1511,åƒè‘‰çœŒ, +1368,ic1512,æ±äº¬éƒ½, +1369,ic1513,神奈å·çœŒ, +1370,ic1514,新潟県, +1371,ic1515,山梨県, +1372,ic1516,長野県, +1373,ic1517,富山県, +1374,ic1518,石å·çœŒ, +1375,ic1519,ç¦äº•çœŒ, +1376,ic1520,å²é˜œçœŒ, +1377,ic1521,é™å²¡çœŒ, +1378,ic1522,愛知県, +1379,ic1523,三é‡çœŒ, +1380,ic1524,滋賀県, +1381,ic1525,京都府, +1382,ic1526,大阪府, +1383,ic1527,兵庫県, +1384,ic1528,奈良県, +1385,ic1529,和歌山県, +1386,ic1530,é³¥å–県, +1387,ic1531,島根県, +1388,ic1532,岡山県, +1389,ic1533,広島県, +1390,ic1534,å±±å£çœŒ, +1391,ic1535,徳島県, +1392,ic1536,香å·çœŒ, +1393,ic1537,愛媛県, +1394,ic1538,高知県, +1395,ic1539,ç¦å²¡çœŒ, +1396,ic1540,ä½è³€çœŒ, +1397,ic1541,長崎県, +1398,ic1542,熊本県, +1399,ic1543,大分県, +1400,ic1544,宮崎県, +1401,ic1545,鹿å…島県, +1402,ic1546,沖縄県, +1403,ic1547,海外, +1404,ic1550,CLASS I, +1405,ic1551,CLASS II, +1406,ic1552,CLASS III, +1407,ic1553,CLASS IV, +1408,ic1554,CLASS V, +1409,ic1555,CLASS VI, +1410,ic1556,CLASS VII, +1411,ic1557,CLASS VIII, +1412,ic1558,CLASS IX, +1413,ic1559,CLASS X, +1414,ic1560,CLASS XI, +1415,ic1561,CLAS XII, +1416,ic1562,CLASS XIII, +1417,ic1563,CLASS XIV, +1418,ic1600,ã‚ã‹ã¤ã(春), +1419,ic1601,ã‚ã‹ã¤ã(å¤ï¼‰, +1420,ic1602,ã‚ã‹ã¤ã(秋), +1421,ic1603,ã‚ã‹ã¤ã(冬), +1422,ic1604,竹, +1423,ic1605,鬼, +1424,ic1606,ç›®, +1425,ic1607,花, +1426,ic1608,æ°´ã¶ãã‚Œ, +1427,ic1609,雨, +1428,ic1610,星, +1429,ic1611,圓, +1430,ic1612,雲, +1431,ic1613,月, +1432,ic1614,梅, +1433,ic1615,雪, +1434,ic1616,ケーキ, +1435,ic1617,ãƒãƒ§ã‚³ãƒ¬ãƒ¼ãƒˆ, +1436,ic1618,猫, +1437,ic1619,プレゼント, +1438,ic1620,葉, +1439,ic1622,HAPPY NAOKI MAEDA BIRTHDAY, +1440,ic1623,七夕, +1441,ic1624,ãƒãƒ­ã‚¦ã‚£ãƒ³, +1442,ic1625,クリスマス, +1443,ic1626,元日, +1444,ic1700,REVå‹ å‹Ÿé›†ä¸­, +1445,ic1701,対戦相手 募集中, +1446,ic1702,スコアアタック中, +1447,ic1703,上手ããªã‚ŠãŸã„, \ No newline at end of file diff --git a/titles/cxb/rss2_data/Item/ItemList_Title.csv b/titles/cxb/rss2_data/Item/ItemList_Title.csv new file mode 100644 index 0000000..3285ead --- /dev/null +++ b/titles/cxb/rss2_data/Item/ItemList_Title.csv @@ -0,0 +1,152 @@ +2000,crossbeats REV. SUNRiSE,1, +2001,crossbeats REV.,1, +2002,SUNSET,5, +2003,ãƒãƒ³ã‚¿ãƒ¼ãƒ‡ãƒ“ュー,1, +2004,隣ã®ãƒãƒ³ã‚¿ãƒ¼,1, +2005,評判ã®ãƒãƒ³ã‚¿ãƒ¼,2, +2006,ã‹ãªã‚Šã®ãƒãƒ³ã‚¿ãƒ¼,2, +2007,素晴らã—ã„ãƒãƒ³ã‚¿ãƒ¼,3, +2008,一æµãƒãƒ³ã‚¿ãƒ¼,3, +2009,å¹»ã®ãƒãƒ³ã‚¿ãƒ¼,4, +2010,ãƒãƒ³ã‚¿ãƒ¼å¸«åŒ ,4, +2011,ï¼¼INNOVATIONï¼,5, +2012,10階ã®ä½äºº,1, +2013,20階ã®ä½äºº,2, +2014,30階ã®ä½äºº,2, +2015,40階ã®ä½äºº,2, +2016,50階ã®ä½äºº,2, +2017,60階ã®ä½äºº,3, +2018,70階ã®ä½äºº,3, +2019,80階ã®ä½äºº,3, +2020,90階ã®ä½äºº,4, +2021,フルコン芸術家[レベル1],1, +2022,フルコン芸術家[レベル2],2, +2023,フルコン芸術家[レベル3],2, +2024,フルコン芸術家[レベル4],2, +2025,フルコン芸術家[レベル5],2, +2026,フルコン芸術家[レベル6],5, +2027,ULTIMATE使ã„[レベル1],1, +2028,ULTIMATE使ã„[レベル2],2, +2029,ULTIMATE使ã„[レベル3],2, +2030,ULTIMATE使ã„[レベル4],2, +2031,ULTIMATE使ã„[レベル5],3, +2032,ULTIMATE使ã„[レベル6],3, +2033,ULTIMATE賢者[レベル1],2, +2034,ULTIMATE賢者[レベル2],2, +2035,ULTIMATE賢者[レベル3],3, +2036,ULTIMATE賢者[レベル4],3, +2037,ULTIMATE賢者[レベル5],4, +2038,ULTIMATE賢者[レベル6],4, +2039,100%マニア[レベル1],1, +2040,100%マニア[レベル2],2, +2041,100%マニア[レベル3],2, +2042,100%マニア[レベル4],2, +2043,100%マニア[レベル5],5, +2044,ãã“ãã“ã§ãã¾ã™,1, +2045,ã¾ã ã¾ã ã§ãã¾ã™,2, +2046,ã¾ãã¾ãã§ãã¾ã™,3, +2047,色々ã§ãã¾ã™,3, +2048,カンペキã«ã§ãã¾ã™,5, +2049,MASTERé”æˆçŽ‡99%余裕ã§ã—ãŸ(笑),3, +2050,UNLIMITEDé”æˆçŽ‡99%余裕ã§ã—ãŸ(白目),3, +2051,ã¡ã‚‡ã“ã£ã¨SUNRISE,2, +2052,ã¾ã ã¾ã SUNRISE,2, +2053,ã„ã‚ã„ã‚SUNRISE,2, +2054,ã‚‚ã£ã¨SUNRISE,2, +2055,ãŸãã•ã‚“SUNRISE,2, +2056,今日も一日 dxb,3, +2057,クロビã£ã¦ã‚‹ï¼Ÿ,3, +2058,dxb dxb dxb dxb,3, +2059,帰ã£ã¦ããŸ!?イノベーター,4, +2060,イノベーションãŠã˜ã•ã‚“,5, +2061,å‹è² å¸«ã®éš£äºº,1, +2062,謎ã‚ã„ãŸå‹è² å¸«,1, +2063,地元ã®å‹è² å¸«,2, +2064,人気者ã®å‹è² å¸«,5, +2065,有åãªå‹è² å¸«,5, +2066,è£MANIA I 覇者,5, +2067,è£MANIA II 覇者,5, +2068,è£Ä°stanbul覇者,5, +2069,真・è£MANIA覇者,5, +2070,真・è£MANIA覇者 極,5, +2071,è£ï¼’ï¼ï¼’ï¼è¦‡è€…,4, +2072,日本全国ノ魂ヲ得タ者,5, +2073,ムリよã‰ã€€ã“れ・・・,2, +2074,刹那ã®è¦‹åˆ‡ã‚Š,3, +2075,ãŠã‚ã¤ã„ã®ã§ã”ã¡ã‚…ã†ã„ãã ã•ã„,3, +2076,ã‚るよãªï¼Ÿ,5, +2077,確実ã«ã‚´ãƒƒï¼ï¼,5, +2078,SEE YOU AGAINã‚„ãª,5, +2079,トリックマスター,3, +2080,岡崎ロケテスター,1, +2081,JAEPO2016å‹¢,5, +2082,å‰ç¥¥å¯ºãƒ­ã‚±ãƒ†ã‚¹ã‚¿ãƒ¼,1, +2083,åˆãƒ¢ãƒŽé£Ÿã„,3, +2084,MY FIRST STORY特集 TOP10,5, +2085,MY FIRST STORY特集 TOP30,5, +2086,MY FIRST STORY特集 TOP50,4, +2087,MY FIRST STORY特集 TOP70,5, +2088,MY FIRST STORY特集 TOP100,5, +2089,MY FIRST STORY特集 TOP300,5, +2090,MY FIRST STORY特集 TOP500,5, +2091,MY FIRST STORY特集 TOP700,5, +2092,MY FIRST STORY特集 TOP1000,2, +2093,ライジングパープラー,1, +2094,世界ニæšé˜ãƒ²éŸ¿ã‚«ã‚»ãƒ¨,2, +2095,「ã„ã¤ã®æ—¥ã‹ãƒ»ãƒ»ãƒ»ãƒ»ã€ãã†è¨€ã£ã¦æ—…ç«‹ã¤åƒ•,3, +2096,é ·ã„ã¦ä½•åº¦ã‚‚手を振るå›,4, +2097,僕ã¯æ­¤å‡¦ã§å›ã‚’å¾…ã¤,5, +2098,RISING PURPLE 覇者,5, +2099,RISING PURPLE 四天王,5, +2100,RISING PURPLE 豪傑,5, +2101,RISING PURPLE 猛者,5, +2102,RISING PURPLE 強者,4, +2103,RISING PURPLE å人,5, +2104,RISING PURPLE é”人,5, +2105,RISING PURPLE 精鋭,5, +2106,RISING PURPLE 入賞,2, +2107,アニメミュージック通ã«ãªã‚Šã¾ã—ãŸ,2, +2108,ディノãƒãƒ«ãƒ‰,1, +2109,ニャンター,2, +2110,ç‹©é­‚,3, +2111,MONSTER HUNTER,4, +2112,モンスターãƒãƒ³ã‚¿ãƒ¼ã‚¯ãƒ­ã‚¹,5, +2113,レベルイェーラー,2, +2114,æ—…ç«‹ã¤èƒŒä¸­ã«ãƒ¦ãƒ«ã‚®ãƒŠã‚¤æ±ºæ„,2, +2115,you're not alone...,3, +2116,キミã«ã€€ä¼ãˆãŸã„よ,4, +2117,キミã¨ã€€é§†ã‘抜ã‘ã¦ã,5, +2118,帰ã£ã¦ããŸãƒ¬ãƒ™ãƒ«ã‚¤ã‚§ãƒ¼ãƒ©ãƒ¼,3, +2119,REBEL YELL 覇者,5, +2120,REBEL YELL 四天王,5, +2121,REBEL YELL 豪傑,5, +2122,REBEL YELL 猛者,5, +2123,REBEL YELL 強者,4, +2124,REBEL YELL å人,5, +2125,REBEL YELL é”人,5, +2126,REBEL YELL 精鋭,5, +2127,REBEL YELL 入賞,2, +2128,昇é¾æ‹³ï¼,1, +2129,ゴメンã­ï¼,2, +2130,神脚美麗,3, +2131,俺より強ã„奴ã«ä¼šã„ã«è¡Œã,4, +2132,The World Warrior,5, +2133,グルコス勢,1, +2134,REVå‹¢ã§ãƒ´å‹¢,5, +2135,ãŠæ–­ãƒªãƒ³ã‚«ã¡ã‚ƒã‚“,5, +2136,I ï½– リンカ,5, +2137,I ï½– Groove Coaster,5, +2138,Groove Master?,5, +2139,イノベーターã®å¼Ÿå­,2, +2140,駆ã‘出ã—イノベーター,1, +2141,ミドルイノベーター,2, +2142,ç«‹æ´¾ãªã‚¤ãƒŽãƒ™ãƒ¼ã‚¿ãƒ¼,2, +2143,ベテンランイノベーター,2, +2144,憧れã®ã‚¤ãƒŽãƒ™ãƒ¼ã‚¿ãƒ¼,3, +2145,特級イノベーター,3, +2146,イノベーター超人,4, +2147,ウルトラ☆イノベーター,4, +2148,カリスマ☆イノベーター,5, +2149,ï¼¼INNOVATION ~改~ï¼,5, +2150,åˆä»£ãƒ¬ãƒ´å‹¢,1, +2151,夜明ã‘å‰,1, \ No newline at end of file diff --git a/titles/cxb/rss2_data/License.csv b/titles/cxb/rss2_data/License.csv new file mode 100644 index 0000000..3122453 --- /dev/null +++ b/titles/cxb/rss2_data/License.csv @@ -0,0 +1,89 @@ +英雄ã®è¨¼ ~ 4Version/カプコンサウンドãƒãƒ¼ãƒ  +© CAPCOM CO., LTD. ALL RIGHTS RESERVED. + +ç¼ç†±ã®åˆƒ ~ ディノãƒãƒ«ãƒ‰ +© CAPCOM CO., LTD. ALL RIGHTS RESERVED. + +å¤ä»£ã®æ¯å¹ã +© CAPCOM CO., LTD. ALL RIGHTS RESERVED. + +Theme of Ryu -SFIV Arrange-/Capcom Sound Team / Hideyuki Fukasawa +©CAPCOM U.S.A., INC. ALL RIGHTS RESERVED. + +Ultra Street Fighter IV/Hideyuki Fukasawa +©CAPCOM U.S.A., INC. ALL RIGHTS RESERVED. + +Theme of Chun-Li -SFIV Arrange-/Capcom Sound Team / Hideyuki Fukasawa +©CAPCOM U.S.A., INC. ALL RIGHTS RESERVED. + +Street Fighter V/Masahiro Aoki +©CAPCOM U.S.A., INC. ALL RIGHTS RESERVED. + +英雄ã®è¨¼ï¼MHF-G 2015 Version +© CAPCOM CO., LTD. ALL RIGHTS RESERVED. + +異ヲ辿リシモノ -対峙-/若林タカツグ +© CAPCOM CO., LTD. ALL RIGHTS RESERVED. + + + + + + + + + + + + + +QLWA(グルーヴコースター 3 リンクフィーãƒãƒ¼ã‚ˆã‚Šï¼‰/t+pazolite +© TAITO CORPORATION 1978,2016 ALL RIGHTS RESERVED. + +グルーヴ・ザ・ãƒãƒ¼ãƒˆï¼ˆã‚°ãƒ«ãƒ¼ãƒ´ã‚³ãƒ¼ã‚¹ã‚¿ãƒ¼ 3 リンクフィーãƒãƒ¼ã‚ˆã‚Šï¼‰/ビートã¾ã‚ŠãŠï¼‹ã‚ã¾ã­ +© TAITO CORPORATION 1978,2016 ALL RIGHTS RESERVED. + +Got hive of Ra(グルーヴコースター 3 リンクフィーãƒãƒ¼ã‚ˆã‚Šï¼‰/E.G.G. +© TAITO CORPORATION 1978,2016 ALL RIGHTS RESERVED. + +LINK LINK FEVER!!!(グルーヴコースター 3 リンクフィーãƒãƒ¼ã‚ˆã‚Šï¼‰/リンカ (CV:豊田èŒçµµï¼‰ +© TAITO CORPORATION 1978,2016 ALL RIGHTS RESERVED. + +SAKURA EXHAUST/RIO HAMAMOTO(BNSI)「太鼓ã®é”人ã€ã‚ˆã‚Š +©BANDAI NAMCO Entertainment Inc. + +カリソメ(グルーヴコースター 3 リンクフィーãƒãƒ¼ã‚ˆã‚Šï¼‰/コンプ(豚乙女) × ichigo(岸田教団 & THE明星ロケッツ) +©上海アリス幻樂団 +© TAITO CORPORATION 1978,2016 ALL RIGHTS RESERVED. + +ãã—ã¦èª°ã‚‚ã„ãªããªã£ãŸï¼ˆã‚°ãƒ«ãƒ¼ãƒ´ã‚³ãƒ¼ã‚¹ã‚¿ãƒ¼ 3 リンクフィーãƒãƒ¼ã‚ˆã‚Šï¼‰/コãƒãƒ¤ã‚·ãƒ¦ã‚¦ãƒ¤(IOSYS) × ã‚ã«ãƒ¼(TaNaBaTa) +©上海アリス幻樂団 +© TAITO CORPORATION 1978,2016 ALL RIGHTS RESERVED. + + + + + + + + + + + + + + + + + +Font Design by Fontworks Inc. +DynaFont is a registered Trademark of DynaComware Taiwan Inc. +Ogg Vorbis is Copyright ©2015, Xiph. Org Foundation +The font used on this product is provided by Hakusyu Fonts co,.Ltd. +キャスティング・ライツクリアランス +æ ªå¼ä¼šç¤¾ãƒ“ーイング +JASRAC許諾第V-1512134å· +e-License許諾番å·GS35000 +VOCALOID and VOCALO are trademarks of Yamaha Corporation. + +OMNiMIX v1.2 diff --git a/titles/cxb/rss2_data/MissionList.csv b/titles/cxb/rss2_data/MissionList.csv new file mode 100644 index 0000000..8928eb1 --- /dev/null +++ b/titles/cxb/rss2_data/MissionList.csv @@ -0,0 +1,4 @@ +MissionID,Text,Type,Value_op,Value,Mcode,Difficulty_op,Difficulty,Level_op,Level,Grade_op,Grade,GaugeType_op,GaugeType,HiSpeed_op,HiSpeed,APP,DAP,FlipV,FlipH,Fullcombo,Combo_op,Combo,ClearRate_op,ClearRate,StartTime,StartEnd,District,CoupleId +0,-,-1,-1,-1,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,1443233520,1843233520,-1,-1,-1,-1,-1,-1,-1,-1, +1,-,-1,-1,-1,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,1443233520,1843233520,-1,-1,-1,-1,-1,-1,-1,-1, +2,ExtraStage��MEGALOMAN[i]A���N���A����,0,-1,-1,megaro,-1,-1,-1,-1,1,9,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,1443233520,1843233520,-1,-1,-1,-1,-1,-1,-1,-1, diff --git a/titles/cxb/rss2_data/MusicArchiveList.csv b/titles/cxb/rss2_data/MusicArchiveList.csv new file mode 100644 index 0000000..3f68dfe --- /dev/null +++ b/titles/cxb/rss2_data/MusicArchiveList.csv @@ -0,0 +1,473 @@ +tutori2,10000,100000,10,10,10,10,10,0,0,0,0,0,0,100000, +tutori3,10000,100000,10,10,10,10,10,0,0,0,0,0,0,100000, +tutori4,10000,10100000,10,10,10,10,10,0,0,0,0,0,0,100000, +tutori6,10050,10100000,10,10,10,10,10,0,0,0,0,0,0,100000, +tutori8,10000,800000,30,150,350,0,0,1,1,1,0,0,0,100000, +sateli,10000,800000,280,490,770,820,170,1,1,1,1,1,0,100300, +nature,10000,40802000,140,240,530,750,50,1,1,1,1,1,3,100301, +purple,10000,800000,220,540,640,730,140,1,1,1,1,1,0,100307, +hearts,10000,800000,180,380,680,770,80,1,1,1,1,1,0,100308, +phasea,10000,800000,160,380,650,750,90,1,1,1,1,1,0,100310, +planet,10000,802000,170,360,490,710,100,1,1,1,1,1,1,100311, +firefo,10000,800000,130,360,570,830,70,1,1,1,1,1,0,100314, +kounen,10000,808000,210,400,660,780,100,1,1,1,1,1,3,100315, +essenc,10000,800000,250,500,700,760,110,1,1,1,1,1,0,100316, +summer,10000,800000,230,570,790,890,130,1,1,1,1,1,0,100317, +tanosi,10000,802000,250,450,700,800,160,1,1,1,1,1,7,100319, +picora,10000,802000,150,380,660,750,80,1,1,1,1,1,1,100320, +devils,10000,800000,270,400,800,0,150,1,1,1,0,1,0,100323, +techno,10000,802000,160,380,510,740,90,1,1,1,1,1,1,100328, +glowww,10000,40802000,170,280,420,600,80,1,1,1,1,1,7,100335, +powerr,10000,800000,190,380,690,790,120,1,1,1,1,1,0,100336, +amater,10000,800000,210,480,650,790,130,1,1,1,1,1,0,100340, +advers,10000,802000,150,480,710,830,90,1,1,1,1,1,1,100349, +venera,10000,800000,150,430,680,750,80,1,1,1,1,1,0,100353, +dazaii,10000,803000,210,430,730,770,120,1,1,1,1,1,3,100357, +thesig,10000,802000,210,380,560,730,100,1,1,1,1,1,3,100365, +hosita,10000,802000,160,360,480,650,100,1,1,1,1,1,9999,100344, +bluede,10000,802000,220,410,580,700,120,1,1,1,1,1,5,100372, +emerao,10000,802000,300,530,850,0,190,1,1,1,0,1,1,100373, +megaro,10000,800000,550,800,930,980,0,1,1,1,1,1,0,100129, +angeli,10000,800000,330,560,820,900,220,1,1,1,1,1,0,100330, +moonli,10000,803000,140,430,610,730,80,1,1,1,1,1,9999,100342, +yumemi,10000,802000,120,350,590,690,60,1,1,1,1,1,9999,100369, +pinkym,10000,800000,240,440,740,810,160,1,1,1,1,1,0,100348, +dynami2,10000,804000,180,510,780,800,80,1,1,1,1,1,0,100370, +reseed3,10000,800000,200,550,760,800,100,1,1,1,1,1,0,100306, +toucho,10000,802000,90,280,440,650,50,1,1,1,1,1,1,100002, +ameoto,10000,802000,120,260,470,630,60,1,1,1,1,1,1,100003, +kimito,10000,800000,100,260,490,660,70,1,1,1,1,1,1,100004, +giantk,10000,808000,250,530,710,780,110,1,1,1,1,1,3,100021, +breakd,10000,803000,230,340,570,740,110,1,1,1,1,1,2,100015, +dazzlj,10000,802000,350,600,800,900,160,1,1,1,1,1,1,100028, +ididid,10000,802000,290,460,720,810,160,1,1,1,1,1,1,100093, +sundro,10000,808000,240,470,750,830,140,1,1,1,1,1,3,100042, +auflcb,10000,800000,130,430,810,0,80,1,1,1,0,1,0,100063, +dennou,10000,801000,290,600,760,870,150,1,1,1,1,1,1,100045, +hokoro,10000,801000,290,570,710,810,140,1,1,1,1,1,1,100068, +landin,10000,802000,260,330,490,670,130,1,1,1,1,1,1,100005, +tomorr,10003,803000,150,240,440,620,80,1,1,1,1,1,7,100362, +daybyd,10003,803000,130,260,380,590,60,1,1,1,1,1,7,100363, +syoujo,10003,802000,190,350,530,780,80,1,1,1,1,1,1,100309, +destru,10003,800000,190,410,620,720,100,1,1,1,1,1,0,100352, +gingat,10005,800000,130,290,460,610,50,1,1,1,1,1,0,100041, +daisak,10005,800000,280,360,600,750,120,1,1,1,1,1,0,100066, +paradi,10005,801000,160,280,530,640,100,1,1,1,1,1,3,100376, +pigooo,10005,800000,190,340,590,840,130,1,1,1,1,1,6,100377, +season,10006,1607000,150,280,440,650,80,1,1,1,1,1,1,100386, +canonn,10006,1607000,170,280,500,830,70,1,1,1,1,1,1,100387, +rhapso,10006,1607000,180,340,620,740,60,1,1,1,1,1,1,100388, +turkis,10006,1607000,190,390,640,840,110,1,1,1,1,1,1,100389, +biohaz,10006,1609000,150,300,510,640,60,1,1,1,1,1,9999,100390, +monhan,10006,1609000,100,260,360,540,50,1,1,1,1,1,9999,100391, +gyakut2,10006,1609000,130,350,430,560,50,1,1,1,1,1,9999,100392, +street,10006,1609000,130,340,470,660,70,1,1,1,1,1,9999,100393, +rockma2,10006,1609000,210,340,490,760,140,1,1,1,1,1,9999,100394, +auflcb3,10007,802000,160,360,660,860,60,1,1,1,1,1,9999,100374, +irohaa,10008,800000,190,410,550,760,120,1,1,1,1,1,0,100325, +ibelie,10008,800000,270,470,780,820,140,1,1,1,1,1,0,100326, +monhan2,10014,1609000,120,240,430,680,60,1,1,1,1,1,9999,100409, +monhan3,10014,1609000,180,280,450,730,80,1,1,1,1,1,9999,100410, +yejiii,10019,800000,220,360,630,790,100,1,1,1,1,1,0,100418, +histor,10019,800000,200,360,560,820,110,1,1,1,1,1,0,100419, +chaset,10019,803000,150,310,580,760,80,1,1,1,1,1,9999,100338, +metall,10021,803000,160,280,570,770,80,1,1,1,1,1,1,100412, +letmeg,10021,802000,180,320,500,720,120,1,1,1,1,1,1,100327, +hontno,10022,801000,120,260,530,660,90,1,1,1,1,1,1,100010, +azitat,10022,800000,240,550,700,830,140,1,1,1,1,1,0,100024, +hellom,10022,801000,180,370,580,720,70,1,1,1,1,1,1,100360, +laught,10022,801000,200,350,510,710,70,1,1,1,1,1,1,100337, +bluede2,10024,801000,160,360,560,810,90,1,1,1,1,1,1,100426, +street2,10025,1609000,210,370,550,730,140,1,1,1,1,1,9999,100423, +street3,10025,1609000,240,380,570,740,130,1,1,1,1,1,9999,100424, +street4,10025,1609000,170,320,510,780,110,1,1,1,1,1,9999,100425, +silbur,10026,801000,200,350,540,750,90,1,1,1,1,1,1,100421, +spicaa,10026,801000,230,360,560,780,100,1,1,1,1,1,1,100422, +tricko,10050,800000,230,340,560,750,110,1,1,1,1,1,0,100438, +thisis,10050,800000,230,370,600,740,120,1,1,1,1,1,4,100435, +rising,10050,800000,230,380,660,850,140,1,1,1,1,1,4,100436, +orbita,10050,803000,200,380,620,740,120,1,1,1,1,1,5,100411, +dddddd,10050,800000,130,330,530,690,90,1,1,1,1,1,5,100433, +pyroma,10050,800000,220,420,700,840,80,1,1,1,1,1,4,100427, +touchn,10050,804000,270,460,680,860,150,1,1,1,1,1,8,100312, +onlyll,10050,802000,210,320,560,690,130,1,1,1,1,1,9999,100359, +upside,10050,802000,180,270,480,670,80,1,1,1,1,1,1,100313, +istanb,10050,806000,410,490,900,980,230,1,1,1,1,1,3,100322, +memori,10050,803000,260,380,650,840,130,1,1,1,1,1,9999,100371, +straye,10050,800000,250,360,610,730,140,1,1,1,1,1,4,100350, +rearhy,10050,802000,170,320,520,690,70,1,1,1,1,1,9999,100358, +hereco,10050,801000,160,340,510,680,110,1,1,1,1,1,4,100432, +thesun,10050,800000,250,400,720,870,130,1,1,1,1,1,4,100441, +sayona,10050,803000,200,340,530,710,100,1,1,1,1,1,9999,100343, +flameu,10050,801000,180,360,570,650,100,1,1,1,1,1,1,100380, +raidon,10050,801000,300,380,580,870,130,1,1,1,1,1,1,100434, +riseup,10050,800000,180,410,670,770,70,1,1,1,1,1,4,100437, +sunglo,10050,801000,180,390,590,720,100,1,1,1,1,1,7,100431, +kinbos,10050,800000,220,380,640,770,120,1,1,1,1,1,3,100439, +densho,10050,800000,280,420,740,900,170,1,1,1,1,1,5,100430, +aiohoo,10050,101000,180,290,420,660,100,1,1,1,1,1,1,100471, +entert,10050,1607000,150,330,540,870,90,1,1,1,1,1,6,100472, +takeit,10050,800000,200,380,650,830,120,1,1,1,1,1,4,100457, +harmon,10050,801000,200,360,490,650,120,1,1,1,1,1,7,100449, +avemar,10050,1607000,160,310,530,750,80,1,1,1,1,1,5,100428, +mateki,10050,1607000,180,350,540,790,100,1,1,1,1,1,5,100429, +lovech,10050,801000,160,300,460,680,80,1,1,1,1,1,7,100445, +akaihe,10051,101000,210,320,500,690,80,1,1,1,1,1,1,100473, +juicys,10051,101000,180,260,450,830,100,1,1,1,1,1,7,100474, +codena,10055,801000,180,350,480,680,90,1,1,1,1,1,1,100468, +groove,10055,1601000,220,400,520,730,100,1,1,1,1,1,103,100475, +kansho,10056,801000,130,270,550,740,70,1,1,1,1,1,9999,100450, +overcl2,10057,809000,230,420,580,740,120,1,1,1,1,1,3,100486, +taikoo,10058,1609000,130,390,500,750,60,1,1,1,1,1,104,100483, +groove2,10058,1601000,150,400,580,850,90,1,1,1,1,1,9999,100480, +overcl,10059,809000,120,350,570,860,80,1,1,1,1,1,4,100487, +notoss,10061,800000,120,420,650,910,80,1,1,1,1,1,4,100466, +machup,10061,801000,170,320,410,710,90,1,1,1,1,1,6,100447, +groove3,10062,2009000,180,340,640,850,100,1,1,1,1,1,105,100488, +groove4,10062,2009000,220,350,500,750,120,1,1,1,1,1,106,100489, +everyt,10063,801000,220,300,740,0,130,1,1,1,0,1,5,100482, +lespri,10063,800000,250,570,800,0,130,1,1,1,0,1,5,100465, +groove5,10064,1609000,240,370,670,0,140,1,1,1,0,1,0,100491, +honeyo,10064,802000,320,630,880,930,240,1,1,1,1,1,6,100490, +groove6,10065,1609000,300,640,790,0,220,1,1,1,0,1,3,100494, +sunglo2,10065,801000,210,360,670,810,110,1,1,1,1,1,6,100495, +fourte,10066,800000,240,500,740,800,140,1,1,1,1,1,5,100498, +monhan4,10066,1609000,120,400,510,620,50,1,1,1,1,1,9999,100496, +monhan5,10066,1609000,120,350,420,650,100,1,1,1,1,1,9999,100497, +darkpa,10068,800000,260,390,700,840,160,1,1,1,1,1,3,100504, +hervor,10068,800000,280,390,730,810,180,1,1,1,1,1,5,100505, +cirnon,10069,409000,240,400,600,790,170,1,1,1,1,1,9999,100499, +marisa,10069,409000,250,410,620,850,180,1,1,1,1,1,9999,100500, +yakini,10069,409000,250,340,580,820,130,1,1,1,1,1,9999,100501, +justic,10069,409000,190,360,570,830,140,1,1,1,1,1,1,100502, +sintyo,10069,409000,250,460,700,830,160,1,1,1,1,1,6,100503, +ascand,10069,800000,320,540,800,900,180,1,1,1,1,1,0,100347, +blackl,10069,800000,190,410,730,840,120,1,1,1,1,1,3,100506, +childr,10069,800000,240,390,560,620,140,1,1,1,1,1,0,100043, +tsukai,10069,802000,190,440,720,760,130,1,1,1,1,1,1,100044, +rideon,10069,802000,290,410,600,800,160,1,1,1,1,1,1,100067, +minest,10070,800000,210,390,620,760,130,1,1,1,1,1,1,100507, +ordine,10070,800000,250,430,730,820,190,1,1,1,1,1,3,100508, +dreamw,10072,800000,260,370,620,750,160,1,1,1,1,1,0,100509, +minerv,10072,800000,320,610,900,0,250,1,1,1,0,1,4,100510, +wannab,10073,800000,90,230,400,650,50,1,1,1,1,1,3,100001, +sekain,10073,800000,260,390,690,780,160,1,1,1,1,1,1,100511, +farawa,10074,800000,230,360,600,760,180,1,1,1,1,1,7,100512, +crissc,10076,800000,370,630,860,910,170,1,1,1,1,1,4,100100, +speedy,10076,800000,220,550,770,0,110,1,1,1,0,1,8,100324, +xxxrev,10076,800000,210,340,560,730,150,1,1,1,1,1,0,100513, +higame,10076,800000,200,300,580,710,130,1,1,1,1,1,3,100016, +theepi,10076,800000,190,400,610,750,140,1,1,1,1,1,3,100022, +anomie,10076,800000,220,380,610,770,150,1,1,1,1,1,0,100023, +crocus,10077,800000,260,370,600,720,150,1,1,1,1,1,7,100524, +lavien,10077,800000,270,410,710,800,180,1,1,1,1,1,5,100546, +megaro2,10100,800000,0,0,990,1000,0,0,0,1,1,0,4,100361, +chipnn,10077,800000,270,340,610,790,160,1,1,1,1,1,6,100541, +yiyoyi,10080,800000,140,330,560,700,70,1,1,1,1,1,3,100007, +binary,10080,800000,170,350,640,890,140,1,1,1,1,1,1,100014, +makaim,10078,2400000,300,500,770,0,230,1,1,1,0,1,3,100054, +gyakut,10078,2400000,150,210,460,640,60,1,1,1,1,1,1,100055, +basara,10078,2400000,190,370,640,730,140,1,1,1,1,1,0,100056, +daybre,10080,201000,160,320,530,720,90,1,1,1,1,1,0,100514, +umiyur,10080,201000,140,280,460,640,80,1,1,1,1,1,1,100515, +chalur,10080,201000,180,400,600,720,140,1,1,1,1,1,9999,100516, +melanc,10080,201000,150,300,500,630,100,1,1,1,1,1,7,100517, +konofu,10080,201000,230,350,620,810,110,1,1,1,1,1,1,100518, +bladem,10078,800000,280,380,630,750,170,1,1,1,1,1,4,100526, +southw,10078,800000,180,270,570,680,120,1,1,1,1,1,7,100536, +ryuuse,10078,800000,210,320,590,0,130,1,1,1,0,1,1,100537, +redhea,10081,401000,270,390,590,720,100,1,1,1,1,1,0,100519, +warnin,10081,401000,250,360,610,740,120,1,1,1,1,1,9999,100520, +topsec,10081,401000,240,340,510,640,130,1,1,1,1,1,9999,100521, +dddoll,10081,401000,260,380,550,630,140,1,1,1,1,1,9999,100522, +tracee,10081,401000,190,310,490,650,90,1,1,1,1,1,0,100548, +drivin,10100,800000,230,400,660,760,80,1,1,1,1,1,7,100111, +genzit,10100,800000,180,460,730,820,120,1,1,1,1,1,0,100118, +aerial,10100,800000,110,280,560,710,50,1,1,1,1,1,1,100039, +einher,10100,800000,290,400,740,800,160,1,1,1,1,1,4,100532, +ariell,10100,800000,190,320,640,730,150,1,1,1,1,1,7,100540, +firstl,10100,800000,250,360,650,770,170,1,1,1,1,1,1,100542, +heartl,10100,800000,230,300,640,0,110,1,1,1,0,1,1,100550, +erasee,10100,800000,220,350,580,680,120,1,1,1,1,1,0,100551, +regene,10100,800000,200,300,560,700,130,1,1,1,1,1,0,100530, +allelu,10100,800000,280,350,640,750,160,1,1,1,1,1,9999,100549, +lighto,10100,800000,250,330,600,740,120,1,1,1,1,1,1,100543, +termin,10100,800000,240,340,630,790,130,1,1,1,1,1,7,100552, +ryuuse2,10101,800000,200,360,620,750,130,1,1,1,1,1,1,100556, +prizmm,10102,800000,210,300,540,0,120,1,1,1,0,1,1,100547, +samalv,10102,800000,190,390,580,770,130,1,1,1,1,1,6,100098, +palpit,10102,800000,290,550,840,920,180,1,1,1,1,1,8,100544, +gainen,10102,801000,260,370,630,0,150,1,1,1,0,1,9999,100558, +moonsh,10102,800000,230,360,620,0,100,1,1,1,0,1,3,100525, +moonki,10102,800000,250,390,640,0,130,1,1,1,0,1,1,100559, +moonri,10102,800000,210,380,580,850,140,1,1,1,1,1,0,100560, +goaway,10103,800000,230,450,590,700,100,1,1,1,1,1,0,100561, +itback,10103,800000,230,380,710,0,120,1,1,1,0,1,3,100567, +redhhh,10103,800000,240,390,770,0,130,1,1,1,0,1,4,100569, +actual,10103,800000,250,380,800,0,140,1,1,1,0,1,0,100568, +zonzon,10066,800000,160,330,630,670,50,1,1,1,1,1,1,100367, +memorm,10103,800000,260,370,730,0,150,1,1,1,0,1,0,100565, +kokoro,10103,800000,200,430,650,690,120,1,1,1,1,1,1,100554, +poweri,10103,800000,260,490,750,910,130,1,1,1,1,1,4,100563, +nisenn,10103,800000,0,0,760,0,0,0,0,1,0,0,8,100555, +yukiya,10103,800000,190,400,610,0,110,1,1,1,0,1,3,100096, +zankyo,10103,800000,180,380,570,740,100,1,1,1,1,1,5,100124, +overlp,10103,800000,170,300,510,0,90,1,1,1,0,1,7,100119, +fracta,10103,800000,310,520,830,0,190,1,1,1,0,1,3,100529, +cantst,10103,800000,230,420,650,0,110,1,1,1,0,1,0,100455, +primaa,10104,800000,180,350,540,750,120,1,1,1,1,1,0,100527, +cyberg,10104,801000,230,350,600,0,120,1,1,1,0,1,0,100448, +freakw,10104,800000,220,420,650,660,130,1,1,1,1,1,0,100018, +aquali,10104,800000,160,340,580,0,110,1,1,1,0,1,4,100006, +takesc,10104,800000,270,370,690,0,100,1,1,1,0,1,1,100572, +cthugh,10104,800000,250,480,730,0,140,1,1,1,0,1,0,100531, +thetaa,10104,800000,210,340,620,0,110,1,1,1,0,1,1,100571, +nekofu,10064,1607000,220,340,570,800,100,1,1,1,1,1,6,100493, +howtru,10074,800000,120,250,530,740,80,1,1,1,1,1,0,100057, +romanc,10104,800000,280,550,780,0,100,1,1,1,0,1,0,100047, +kotobu,10104,800000,320,710,900,0,250,1,1,1,0,1,0,100573, +xmasss,10017,1607000,180,380,560,770,80,1,1,1,1,1,101,100417, +galaxy,10000,101000,160,320,430,670,100,1,1,1,1,1,0,100600, +rebell,10000,800000,490,630,910,0,0,1,1,1,0,0,0,100601, +anothe,10000,800000,270,370,730,760,0,1,1,1,1,0,0,100602, +addict,10000,800000,200,340,520,620,0,1,1,1,1,0,0,100603, +dirtyy,10000,800000,150,280,590,740,0,1,1,1,1,0,0,100604, +levelf,10000,101000,110,280,450,630,50,1,1,1,1,1,0,100605, +omnive,10000,800000,340,520,830,860,0,1,1,1,1,0,0,100606, +kakuse,10000,800000,170,550,750,0,0,1,1,1,0,0,0,100607, +unbeli,10000,101000,130,260,380,620,70,1,1,1,1,1,0,100608, +sonzai,10000,800000,260,400,590,660,0,1,1,1,1,0,0,100609, +okonik,10000,800000,260,450,670,0,0,1,1,1,0,0,0,100610, +crssho,10000,800000,350,600,850,0,100,1,1,1,0,1,0,100611, +reanim,10000,800000,280,440,700,800,0,1,1,1,1,0,0,100612, +kamino,10000,800000,400,620,780,0,150,1,1,1,0,1,0,100613, +fiveee,10000,101000,180,370,610,710,100,1,1,1,1,1,0,100614, +granda,10000,800000,210,380,790,0,0,1,1,1,0,0,0,100615, +fronti2,10000,800000,460,690,890,0,90,1,1,1,0,1,0,100616, +saigon,10000,800000,190,310,570,0,0,1,1,1,0,0,0,100617, +replay,10000,101000,180,440,630,700,80,1,1,1,1,1,0,100618, +mousou,10000,800000,160,260,540,0,0,1,1,1,0,0,0,100619, +aheadd,10000,101000,130,250,350,580,70,1,1,1,1,1,0,100620, +musicr1,10000,800000,220,330,580,740,120,1,1,1,1,1,0,100621, +getthe,10000,101000,170,370,490,660,60,1,1,1,1,1,0,100622, +design,10000,800000,150,390,680,690,0,1,1,1,1,0,0,100623, +garnet,10000,800000,260,460,700,940,0,1,1,1,1,0,0,100624, +hopesb,10000,101000,100,250,440,610,70,1,1,1,1,1,0,100625, +shooti,10000,101000,150,370,490,690,70,1,1,1,1,1,0,100626, +dangan,10000,800000,280,580,810,0,0,1,1,1,0,0,0,100627, +impact,10000,800000,240,600,720,900,200,1,1,1,1,1,0,100628, +lightm,10000,101000,260,330,540,710,110,1,1,1,1,1,0,100629, +miiroo,10000,101000,220,390,580,680,110,1,1,1,1,1,0,100630, +voiceo,10000,800000,180,340,580,590,0,1,1,1,1,0,0,100631, +cosmol,10000,800000,360,640,870,0,250,1,1,1,0,1,0,100632, +vividd,10000,101000,160,350,550,650,90,1,1,1,1,1,0,100633, +splash,10000,800000,260,500,710,0,0,1,1,1,0,0,0,100634, +donuth,10000,201000,220,400,540,800,110,1,1,1,1,1,0,100635, +senbon,10000,201000,200,280,540,740,120,1,1,1,1,1,0,100636, +kmtyju,10000,101000,240,310,570,740,120,1,1,1,1,1,0,100637, +fronti,10000,800000,480,650,820,0,130,1,1,1,0,1,0,100638, +nueraa,10000,800000,220,430,750,530,0,1,1,1,1,0,0,100639, +childe,10000,101000,90,240,340,560,40,1,1,1,1,1,0,100640, +dazzli2,10000,800000,350,600,820,0,190,1,1,1,0,1,0,100641, +perfec,10000,800000,390,640,780,0,0,1,1,1,0,0,0,100642, +flower,10000,101000,70,200,400,650,60,1,1,1,1,1,0,100643, +frgmnt,10000,800000,330,630,740,650,100,1,1,1,1,1,0,100644, +headph,10000,800000,240,320,520,0,0,1,1,1,0,0,0,100645, +crsang,10000,800000,270,530,670,0,130,1,1,1,0,1,0,100646, +musicr4,10000,800000,190,320,580,0,120,1,1,1,0,1,0,100647, +imaxim,10000,800000,440,690,900,870,0,1,1,1,1,0,0,100648, +azitat2,10000,800000,230,520,660,0,80,1,1,1,0,1,0,100649, +dynami,10000,800000,260,540,680,0,110,1,1,1,0,1,0,100650, +incave,10000,800000,220,440,760,780,0,1,1,1,1,0,0,100651, +aktuki,10000,800000,260,580,840,0,100,1,1,1,0,1,0,100652, +kindof,10000,800000,140,290,480,0,0,1,1,1,0,0,0,100653, +mikaku,10000,800000,190,310,540,0,0,1,1,1,0,0,0,100654, +strang,10000,800000,120,280,550,0,0,1,1,1,0,0,0,100655, +hesper,10000,800000,360,610,920,930,0,1,1,1,1,0,0,100656, +breaka,10000,101000,150,310,450,680,70,1,1,1,1,1,0,100657, +myname,10000,800000,60,140,300,570,0,1,1,1,1,0,0,100658, +amaiko,10000,800000,150,370,600,0,0,1,1,1,0,0,0,100659, +reseed2,10000,800000,220,470,630,0,0,1,1,1,0,0,0,100660, +kingst,10000,800000,380,630,740,0,120,1,1,1,0,1,0,100661, +ramram,10000,800000,230,340,670,0,0,1,1,1,0,0,0,100662, +murasa,10000,800000,280,410,760,0,0,1,1,1,0,0,0,100663, +happyd,10000,800000,220,410,730,790,180,1,1,1,1,1,0,100664, +izimed,10000,101000,190,390,690,770,90,1,1,1,1,1,0,100665, +wastel,10000,800000,40,120,230,400,0,1,1,1,1,0,0,100666, +assign,10000,800000,260,430,610,620,0,1,1,1,1,0,0,100667, +jahaci,10000,800000,170,290,590,0,0,1,1,1,0,0,0,100668, +hisuii,10000,800000,220,470,700,0,0,1,1,1,0,0,0,100669, +godkno,10000,101000,100,260,450,640,60,1,1,1,1,1,0,100670, +roadof,10000,101000,150,360,500,750,70,1,1,1,1,1,0,100671, +rokuch,10000,301000,210,350,620,810,110,1,1,1,1,1,0,100672, +valent,10000,800000,270,330,590,770,100,1,1,1,1,1,0,100673, +unfini,10000,101000,160,320,500,710,80,1,1,1,1,1,0,100674, +auflcb2,10000,800000,220,370,750,0,100,1,1,1,0,1,0,100675, +burnin,10000,800000,180,280,600,850,150,1,1,1,1,1,0,100676, +sphere,10000,800000,200,380,730,0,0,1,1,1,0,0,0,100677, +dropou,10000,101000,170,310,460,690,140,1,1,1,1,1,0,100678, +xencou,10000,101000,200,320,520,600,80,1,1,1,1,1,0,100679, +killyk,10000,101000,130,420,630,760,60,1,1,1,1,1,0,100680, +missil,10000,800000,160,380,590,0,0,1,1,1,0,0,0,100681, +burstt,10000,101000,120,250,460,630,70,1,1,1,1,1,0,100682, +musicr2,10000,800000,220,330,580,0,120,1,1,1,0,1,0,100683, +isingl,10000,800000,250,440,800,0,120,1,1,1,0,1,0,100684, +lvless,10000,800000,230,380,600,0,0,1,1,1,0,0,0,100685, +sapphi,10000,800000,290,440,810,0,0,1,1,1,0,0,0,100686, +musicr3,10000,800000,190,320,580,720,120,1,1,1,1,1,0,100687, +deeout,10000,800000,180,340,630,810,0,1,1,1,1,0,0,100688, +sugars,10000,101000,170,300,420,660,60,1,1,1,1,1,0,100689, +mercur,10000,800000,140,350,660,0,0,1,1,1,0,0,0,100690, +zizizi,10000,800000,300,570,880,960,0,1,1,1,1,0,0,100691, +wegooo,10000,101000,180,340,540,680,100,1,1,1,1,1,0,100692, +alonee,10000,101000,110,210,360,480,50,1,1,1,1,1,0,100693, +nuheat,10000,800000,290,440,650,850,0,1,1,1,1,0,0,100694, +granro,10000,101000,150,280,430,600,80,1,1,1,1,1,0,100695, +sister,10000,101000,100,270,460,630,70,1,1,1,1,1,0,100696, +lotusl,10000,800000,200,360,640,0,0,1,1,1,0,0,0,100697, +yukari,10000,800000,310,500,760,840,0,1,1,1,1,0,0,100698, +flawli,10000,101000,170,300,400,590,80,1,1,1,1,1,0,100699, +nightf,10000,800000,150,280,460,710,0,1,1,1,1,0,0,100700, +random,10000,800000,0,0,0,0,0,0,0,0,0,0,0,100701, +wiwwtw,10000,800000,260,380,620,0,0,1,1,1,0,0,0,100702, +inneru,10000,800000,220,360,480,670,90,1,1,1,1,1,0,100703, +taishi,10000,800000,190,350,580,0,0,1,1,1,0,0,0,100704, +daysss,10000,800000,380,590,810,810,0,1,1,1,1,0,0,100705, +bokuwa,10000,101000,230,340,550,690,160,1,1,1,1,1,0,100706, +showww,10000,800000,180,350,510,790,150,1,1,1,1,1,0,100707, +nevers,10000,101000,260,320,650,750,150,1,1,1,1,1,0,100708, +bleeze,10000,101000,160,310,470,620,90,1,1,1,1,1,0,100709, +dreami,10000,800000,140,370,650,0,0,1,1,1,0,0,0,100710, +allune,10000,110002000,140,350,710,0,0,1,1,1,0,0,0,100711, +always,10000,110002000,130,270,490,0,0,1,1,1,0,0,0,100712, +anomie2,10000,110002000,160,430,840,0,0,1,1,1,0,0,0,100713, +aquali2,10000,110002000,220,430,600,810,0,1,1,1,1,0,0,100714, +astaro,10000,110002000,230,400,740,0,0,1,1,1,0,0,0,100715, +bassan,10000,110002000,200,320,660,0,0,1,1,1,0,0,0,100716, +zonzon2,10000,110002000,130,270,680,750,0,1,1,1,1,0,0,100717, +bouled,10000,110002000,190,300,570,0,0,1,1,1,0,0,0,100718, +brandn,10000,110002000,90,390,660,720,0,1,1,1,1,0,0,100719, +bravee,10000,110002000,350,600,820,0,250,1,1,1,0,0,0,100720, +breakd2,10000,110002000,340,640,740,0,0,1,1,1,0,0,0,100721, +buffet,10000,110002000,380,550,680,0,300,1,1,1,0,0,0,100722, +buzzke,10000,110002000,180,330,580,770,0,1,1,1,1,0,0,100723, +cashhh,10000,110002000,190,250,640,0,0,1,1,1,0,0,0,100724, +cloudb,10000,110002000,370,660,740,0,250,1,1,1,0,0,0,100725, +clouds,10000,110002000,130,250,470,0,0,1,1,1,0,0,0,100726, +codepa,10000,110002000,290,550,700,0,150,1,1,1,0,0,0,100727, +comear,10000,110002000,380,560,830,0,250,1,1,1,0,0,0,100728, +crysta,10000,110002000,370,560,810,0,300,1,1,1,0,0,0,100729, +curseo,10000,110002000,220,360,740,0,0,1,1,1,0,0,0,100730, +datami,10000,110002000,180,360,660,0,0,1,1,1,0,0,0,100731, +defaul,10000,110002000,210,330,480,0,0,1,1,1,0,0,0,100732, +design2,10000,110002000,250,430,680,0,0,1,1,1,0,0,0,100733, +diamon,10000,110002000,100,260,330,0,0,1,1,1,0,0,0,100734, +dispel,10000,110002000,280,480,800,0,0,1,1,1,0,0,0,100735, +distan,10000,110002000,200,300,680,0,0,1,1,1,0,0,0,100736, +dokibl,10000,110002000,150,230,670,0,0,1,1,1,0,0,0,100737, +dontwa,10000,110002000,130,340,690,0,0,1,1,1,0,0,0,100738, +drgirl,10000,110002000,190,350,540,730,0,1,1,1,1,0,0,100739, +eterna,10000,110002000,120,210,390,0,0,1,1,1,0,0,0,100740, +everkr,10000,110002000,180,290,410,0,0,1,1,1,0,0,0,100741, +everwh,10000,110002000,200,310,580,0,0,1,1,1,0,0,0,100742, +farthe,10000,110002000,300,560,780,870,0,1,1,1,1,0,0,100743, +filame,10000,110002000,230,380,630,0,0,1,1,1,0,0,0,100744, +flameu2,10000,110002000,170,240,590,0,0,1,1,1,0,0,0,100745, +freeee,10000,110002000,190,390,690,0,0,1,1,1,0,0,0,100746, +funkyb2,10000,110002000,210,340,560,0,0,1,1,1,0,0,0,100747, +granda2,10000,110002000,240,410,730,830,0,1,1,1,1,0,0,100748, +hsphsp,10000,110002000,120,250,690,0,0,1,1,1,0,0,0,100749, +halluc,10000,110002000,400,520,870,0,0,1,1,1,0,0,0,100750, +indigo,10000,110002000,170,330,500,750,0,1,1,1,1,0,0,100751, +inters,10000,110002000,250,420,770,0,0,1,1,1,0,0,0,100752, +incave2,10000,110002000,310,570,880,0,0,1,1,1,0,0,0,100753, +ioniza,10000,110002000,170,340,700,850,0,1,1,1,1,0,0,100754, +guilty,10000,110002000,150,280,500,0,0,1,1,1,0,0,0,100755, +keraun,10000,110002000,250,520,790,0,0,1,1,1,0,0,0,100756, +landin2,10000,110002000,200,340,590,660,0,1,1,1,1,0,0,100757, +videog,10000,110002000,210,370,620,0,0,1,1,1,0,0,0,100758, +loseyo,10000,110002000,200,300,710,0,0,1,1,1,0,0,0,100759, +machin,10000,110002000,120,280,720,0,0,1,1,1,0,0,0,100760, +makeit,10000,110002000,110,240,480,0,0,1,1,1,0,0,0,100761, +daydre,10000,110002000,190,360,800,0,0,1,1,1,0,0,0,100762, +metron,10000,110002000,200,440,710,0,0,1,1,1,0,0,0,100763, +milkyw,10000,110002000,220,310,600,0,0,1,1,1,0,0,0,100764, +nayuta,10000,110002000,170,370,680,0,0,1,1,1,0,0,0,100766, +nightm,10000,110002000,200,490,730,0,0,1,1,1,0,0,0,100767, +otherw,10000,110002000,230,410,760,0,0,1,1,1,0,0,0,100768, +overth,10000,110002000,330,570,820,0,250,1,1,1,0,0,0,100769, +uuuuuu,10000,110002000,230,370,740,0,0,1,1,1,0,0,0,100770, +rainin,10000,110002000,160,410,690,0,0,1,1,1,0,0,0,100771, +raisey,10000,110002000,230,550,750,0,150,1,1,1,0,0,0,100772, +resona,10000,110002000,170,320,640,0,0,1,1,1,0,0,0,100773, +reuniv,10000,110002000,140,230,410,0,0,1,1,1,0,0,0,100774, +rhythm,10000,110002000,370,560,780,0,250,1,1,1,0,0,0,100775, +rushhh,10000,110002000,250,370,750,0,0,1,1,1,0,0,0,100776, +steeee,10000,110002000,300,580,870,0,0,1,1,1,0,0,0,100777, +sangey,10000,110002000,270,470,850,0,0,1,1,1,0,0,0,100778, +senpai,10000,110002000,380,540,770,0,250,1,1,1,0,0,0,100779, +sestea,10000,110002000,270,470,760,0,0,1,1,1,0,0,0,100780, +silver,10000,110002000,280,400,690,0,0,1,1,1,0,0,0,100781, +sodama,10000,110002000,200,400,650,0,0,1,1,1,0,0,0,100782, +stardu,10000,110002000,190,330,640,0,0,1,1,1,0,0,0,100783, +starti,10000,110002000,170,310,540,700,0,1,1,1,1,0,0,100784, +sunday,10000,110002000,180,290,460,670,0,1,1,1,1,0,0,100785, +sundro2,10000,110002000,300,480,790,820,0,1,1,1,1,0,0,100786, +sunnyd,10000,110002000,230,380,590,0,0,1,1,1,0,0,0,100787, +superl,10000,110002000,150,320,590,0,0,1,1,1,0,0,0,100788, +switch,10000,110002000,160,350,690,0,0,1,1,1,0,0,0,100789, +theepi2,10000,110002000,220,370,650,0,0,1,1,1,0,0,0,100790, +epipha,10000,110002000,150,300,700,0,0,1,1,1,0,0,0,100791, +thekin,10000,110002000,220,520,750,0,0,1,1,1,0,0,0,100792, +timele,10000,110002000,160,330,720,0,0,1,1,1,0,0,0,100793, +tokyoo,10000,110002000,150,330,710,0,0,1,1,1,0,0,0,100794, +toooma,10000,110002000,300,510,770,0,0,1,1,1,0,0,0,100795, +toucho2,10000,110002000,170,320,520,780,0,1,1,1,1,0,0,100796, +tayuta,10000,110002000,260,350,720,0,0,1,1,1,0,0,0,100797, +ultrix,10000,110002000,270,450,760,0,0,1,1,1,0,0,0,100798, +underw,10000,110002000,290,460,690,860,0,1,1,1,1,0,0,100799, +virtua,10000,110002000,150,350,630,0,0,1,1,1,0,0,0,100800, +voiceo2,10000,110002000,140,380,670,0,0,1,1,1,0,0,0,100801, +wannab2,10000,110002000,260,410,690,0,0,1,1,1,0,0,0,100802, +wiwwtw2,10000,110002000,200,430,670,720,0,1,1,1,1,0,0,100803, +wingso,10000,110002000,200,530,710,0,0,1,1,1,0,0,0,100804, +winter,10000,110002000,140,240,410,0,0,1,1,1,0,0,0,100805, +iineee,10000,110002000,210,400,810,0,0,1,1,1,0,0,0,100806, +illumi,10000,110002000,100,250,460,630,0,1,1,1,1,0,0,100807, +yellll,10000,110002000,80,170,520,0,0,1,1,1,0,0,0,100808, +eschat,10000,110002000,360,570,770,0,250,1,1,1,0,0,0,100809, +counte,10000,110002000,290,340,710,0,0,1,1,1,0,0,0,100810, +gimcho,10000,110002000,180,390,700,0,0,1,1,1,0,0,0,100811, +surviv,10000,110002000,240,400,650,0,0,1,1,1,0,0,0,100812, +turkis3,10000,110002000,60,200,480,0,0,1,1,1,0,0,0,100814, +picora2,10000,110002000,280,530,800,0,0,1,1,1,0,0,0,100815, +fortis,10000,110002000,200,370,530,0,0,1,1,1,0,0,0,100816, +hedban,10000,110002000,160,430,660,0,0,1,1,1,0,0,0,100817, +megitu,10000,110002000,150,300,490,0,0,1,1,1,0,0,0,100818, +rockma,10000,110002000,270,480,730,0,0,1,1,1,0,0,0,100819, +kounen2,10000,110002000,210,430,730,0,0,1,1,1,0,0,0,100820, +saisyu,10000,110002000,180,360,560,0,0,1,1,1,0,0,0,100821, +yuukan,10000,110002000,220,330,780,0,0,1,1,1,0,0,0,100822, +modern,10000,110002000,200,320,560,0,0,1,1,1,0,0,0,100823, +miraie,10000,110002000,210,350,660,0,0,1,1,1,0,0,0,100824, +ranfes,10000,110002000,200,420,650,0,0,1,1,1,0,0,0,100825, +nemure,10000,110002000,150,380,670,760,0,1,1,1,1,0,0,100826, +yuwaku,10000,110002000,150,260,430,0,0,1,1,1,0,0,0,100827, +dontst,10000,110002000,150,320,560,700,0,1,1,1,1,0,0,100828, +mottai,10000,110002000,100,260,360,0,0,1,1,1,0,0,0,100829, +slysly,10000,110002000,100,330,580,0,0,1,1,1,0,0,0,100830, +lookam,10000,110002000,170,340,670,0,0,1,1,1,0,0,0,100831, +feverr,10000,110002000,280,480,680,0,0,1,1,1,0,0,0,100832, +fashio,10000,110002000,80,240,390,0,0,1,1,1,0,0,0,100833, +hagito,10000,110002000,120,260,500,0,0,1,1,1,0,0,0,100834, +invade,10000,110002000,100,280,470,0,0,1,1,1,0,0,0,100835, +ainoch,10000,110002000,170,400,590,0,0,1,1,1,0,0,0,100836, +nakama,10000,110002000,140,320,530,0,0,1,1,1,0,0,0,100837, +ninjar,10000,110002000,80,230,410,650,0,1,1,1,1,0,0,100838, +parall,10000,110002000,140,350,610,0,0,1,1,1,0,0,0,100839, +yukifu,10000,110002000,130,290,510,0,0,1,1,1,0,0,0,100840, +furiso,10000,110002000,120,240,440,740,0,1,1,1,1,0,0,100841, +honeyj,10064,802000,320,630,880,930,240,1,1,1,1,1,6,100842, +emeraj,10000,802000,300,530,850,0,190,1,1,1,0,1,1,100843, +dazzlo,10000,802000,350,600,800,900,160,1,1,1,1,1,1,100844, +shares,10000,800000,0,0,0,0,0,0,0,0,0,0,0,100844, diff --git a/titles/cxb/rss2_data/NewsList.csv b/titles/cxb/rss2_data/NewsList.csv new file mode 100644 index 0000000..258a566 --- /dev/null +++ b/titles/cxb/rss2_data/NewsList.csv @@ -0,0 +1,9 @@ +1,1,1601510400,4096483201,1,0,0,0,1,0,news1054,,,1,1, +2,1,1601510400,4096483201,1,0,0,0,1,0,news1090,,,1,1, +3,1,1601510400,4096483201,1,0,0,0,1,0,news1091,,,1,1, +4,1,1601510400,4096483201,1,0,0,0,1,0,news3001,,,1,1, +5,1,1601510400,4096483201,0,0,0,1,1,0,news9000,,,1,1, +6,1,1601510400,4096483201,1,0,0,0,1,0,news9001,,,1,1, +7,1,1601510400,4096483201,1,0,0,0,1,0,news9002,,,1,1, +8,1,1601510400,4096483201,1,0,0,0,1,0,news9003,,,1,1, +9,1,1601510400,4096483201,1,0,0,0,1,0,news9004,,,1,1, diff --git a/titles/cxb/rss2_data/Partner0000.csv b/titles/cxb/rss2_data/Partner0000.csv new file mode 100644 index 0000000..b26e80b --- /dev/null +++ b/titles/cxb/rss2_data/Partner0000.csv @@ -0,0 +1,101 @@ +1,0,100,100,100,100,100,100,100,100,0,0, +2,100,110,101,101,103,101,101,101,101,1000,0, +3,210,120,102,102,106,102,102,102,102,0,0, +4,331,130,103,103,109,103,103,103,103,0,0, +5,464,140,104,104,112,104,104,104,104,1001,0, +6,610,150,105,105,115,105,105,105,105,1002,0, +7,771,160,106,106,118,106,106,106,106,1003,0, +8,948,170,107,107,121,107,107,107,107,0,0, +9,1143,180,108,108,124,108,108,108,108,0,0, +10,1357,190,109,109,127,109,109,109,109,1004,0, +11,1593,200,110,110,130,110,110,110,110,0,0, +12,1853,210,111,111,133,111,111,111,111,0,0, +13,2138,220,112,112,136,112,112,112,112,0,0, +14,2452,230,113,113,139,113,113,113,113,0,0, +15,2797,240,114,114,142,114,114,114,114,1005,0, +16,3177,250,115,115,145,115,115,115,115,0,0, +17,3594,260,116,116,148,116,116,116,116,0,0, +18,4054,270,117,117,151,117,117,117,117,0,0, +19,4559,280,118,118,154,118,118,118,118,0,0, +20,5115,290,119,119,157,119,119,119,119,1006,1, +21,5727,300,120,120,160,120,120,120,120,0,1, +22,6400,310,121,121,163,121,121,121,121,0,1, +23,7140,320,122,122,166,122,122,122,122,0,1, +24,7954,330,123,123,169,123,123,123,123,0,1, +25,8849,340,124,124,172,124,124,124,124,0,1, +26,9834,350,125,125,175,125,125,125,125,0,1, +27,10918,360,126,126,178,126,126,126,126,0,1, +28,12109,370,127,127,181,127,127,127,127,0,1, +29,13420,380,128,128,184,128,128,128,128,0,1, +30,14863,390,129,129,187,129,129,129,129,0,1, +31,16449,400,130,130,190,130,130,130,130,0,1, +32,18194,410,131,131,193,131,131,131,131,0,1, +33,20113,420,132,132,196,132,132,132,132,0,1, +34,22225,430,133,133,199,133,133,133,133,0,1, +35,24547,440,134,134,202,134,134,134,134,0,1, +36,27102,450,135,135,205,135,135,135,135,0,1, +37,29912,460,136,136,208,136,136,136,136,0,1, +38,33003,470,137,137,211,137,137,137,137,0,1, +39,36404,480,138,138,214,138,138,138,138,0,1, +40,40144,490,139,139,217,139,139,139,139,0,1, +41,44259,500,140,140,220,140,140,140,140,0,1, +42,48785,510,141,141,223,141,141,141,141,0,1, +43,53763,520,142,142,226,142,142,142,142,0,1, +44,59240,530,143,143,229,143,143,143,143,0,1, +45,65264,540,144,144,232,144,144,144,144,0,1, +46,71890,550,145,145,235,145,145,145,145,0,1, +47,79179,560,146,146,238,146,146,146,146,0,1, +48,87197,570,147,147,241,147,147,147,147,0,1, +49,96017,580,148,148,244,148,148,148,148,0,1, +50,105718,590,149,149,247,149,149,149,149,0,2, +51,116390,600,150,150,250,150,150,150,150,0,2, +52,128129,610,151,151,253,151,151,151,151,0,2, +53,141042,620,152,152,256,152,152,152,152,0,2, +54,155247,630,153,153,259,153,153,153,153,0,2, +55,170871,640,154,154,262,154,154,154,154,0,2, +56,188059,650,155,155,265,155,155,155,155,0,2, +57,206965,660,156,156,268,156,156,156,156,0,2, +58,227761,670,157,157,271,157,157,157,157,0,2, +59,250637,680,158,158,274,158,158,158,158,0,2, +60,275801,690,159,159,277,159,159,159,159,0,2, +61,303481,700,160,160,280,160,160,160,160,0,2, +62,333929,710,161,161,283,161,161,161,161,0,2, +63,367422,720,162,162,286,162,162,162,162,0,2, +64,404265,730,163,163,289,163,163,163,163,0,2, +65,444791,740,164,164,292,164,164,164,164,0,2, +66,489370,750,165,165,295,165,165,165,165,0,2, +67,538407,760,166,166,298,166,166,166,166,0,2, +68,592348,770,167,167,301,167,167,167,167,0,2, +69,651683,780,168,168,304,168,168,168,168,0,2, +70,716951,790,169,169,307,169,169,169,169,0,2, +71,788746,800,170,170,310,170,170,170,170,0,2, +72,867721,810,171,171,313,171,171,171,171,0,2, +73,954593,820,172,172,316,172,172,172,172,0,2, +74,1050153,830,173,173,319,173,173,173,173,0,2, +75,1155268,840,174,174,322,174,174,174,174,0,2, +76,1270895,850,175,175,325,175,175,175,175,0,2, +77,1398084,860,176,176,328,176,176,176,176,0,2, +78,1537993,870,177,177,331,177,177,177,177,0,2, +79,1691892,880,178,178,334,178,178,178,178,0,2, +80,1861182,890,179,179,337,179,179,179,179,0,2, +81,2047400,900,180,180,340,180,180,180,180,0,2, +82,2252240,910,181,181,343,181,181,181,181,0,2, +83,2477564,920,182,182,346,182,182,182,182,0,2, +84,2725420,930,183,183,349,183,183,183,183,0,2, +85,2998062,940,184,184,352,184,184,184,184,0,2, +86,3297969,950,185,185,355,185,185,185,185,0,2, +87,3627865,960,186,186,358,186,186,186,186,0,2, +88,3990752,970,187,187,361,187,187,187,187,0,2, +89,4389927,980,188,188,364,188,188,188,188,0,2, +90,4829020,990,189,189,367,189,189,189,189,0,2, +91,5312022,1000,190,190,370,190,190,190,190,0,2, +92,5843324,1010,191,191,373,191,191,191,191,0,2, +93,6427757,1020,192,192,376,192,192,192,192,0,2, +94,7070633,1030,193,193,379,193,193,193,193,0,2, +95,7777796,1040,194,194,382,194,194,194,194,0,2, +96,8555676,1050,195,195,385,195,195,195,195,0,2, +97,9411343,1060,196,196,388,196,196,196,196,0,2, +98,10352578,1070,197,197,391,197,197,197,197,0,2, +99,11387935,1080,198,198,394,198,198,198,198,0,2, +100,12526829,1090,199,199,397,199,199,199,199,0,2, + diff --git a/titles/cxb/rss2_data/Shop/ShopList_Icon.csv b/titles/cxb/rss2_data/Shop/ShopList_Icon.csv new file mode 100644 index 0000000..3467344 --- /dev/null +++ b/titles/cxb/rss2_data/Shop/ShopList_Icon.csv @@ -0,0 +1,452 @@ +1000,1,10000,1,-1,-,1412103600,4096483201,ic0000,10,2,,1,,1,-, +1001,2,10000,1,-1,-,1412103600,4096483201,ic1718,10,2,,1,,1,-, +1002,3,10000,1,-1,-,1412103600,4096483201,ic0001,10,2,,1,,1,-, +1003,4,10000,1,-1,-,1412103600,4096483201,ic0002,10,2,,1,,1,-, +1004,5,10000,1,-1,-,1412103600,4096483201,ic0003,10,2,,1,,1,-, +1005,6,10000,1,-1,-,1412103600,4096483201,ic0004,10,2,,1,,1,-, +1006,7,10000,1,-1,-,1412103600,4096483201,ic0005,10,2,,1,,1,-, +1007,8,10000,1,-1,-,1412103600,4096483201,ic0006,10,2,,1,,1,-, +1008,9,10000,1,-1,-,1412103600,4096483201,ic0007,10,2,,1,,1,-, +1009,10,10000,1,-1,-,1412103600,4096483201,ic0008,10,2,,1,,1,-, +1010,11,10000,1,-1,-,1412103600,4096483201,ic0009,10,2,,1,,1,-, +1011,12,10000,1,-1,-,1412103600,4096483201,ic0010,10,2,,1,,1,-, +1012,13,10000,1,-1,-,1412103600,4096483201,ic0011,10,2,,1,,1,-, +1013,14,10000,1,-1,-,1412103600,4096483201,ic0012,10,2,,1,,1,-, +1014,15,10000,1,-1,-,1412103600,4096483201,ic0013,10,2,,1,,1,-, +1015,16,10000,1,-1,-,1412103600,4096483201,ic0014,10,2,,1,,1,-, +1016,17,10000,1,-1,-,1412103600,4096483201,ic0015,10,2,,1,,1,-, +1017,18,10000,1,-1,-,1412103600,4096483201,ic0016,10,2,,1,,1,-, +1018,19,10000,1,-1,-,1412103600,4096483201,ic0017,10,2,,1,,1,-, +1019,20,10000,1,-1,-,1412103600,4096483201,ic0018,10,2,,1,,1,-, +1020,21,10000,1,-1,-,1412103600,4096483201,ic0019,10,2,,1,,1,-, +1021,22,10000,1,-1,-,1412103600,4096483201,ic0020,10,2,,1,,1,-, +1022,23,10000,1,-1,-,1412103600,4096483201,ic0021,10,2,,1,,1,-, +1023,24,10000,1,-1,-,1412103600,4096483201,ic0022,10,2,,1,,1,-, +1024,25,10000,1,-1,-,1412103600,4096483201,ic0023,10,2,,1,,1,-, +1025,26,10000,1,-1,-,1412103600,4096483201,ic0024,10,2,,1,,1,-, +1026,27,10000,1,-1,-,1412103600,4096483201,ic0025,10,2,,1,,1,-, +1027,28,10000,1,-1,-,1412103600,4096483201,ic0026,10,2,,1,,1,-, +1028,29,10000,1,-1,-,1412103600,4096483201,ic0027,10,2,,1,,1,-, +1029,30,10000,1,-1,-,1412103600,4096483201,ic0029,10,2,,1,,1,-, +1030,31,10000,1,-1,-,1412103600,4096483201,ic0030,10,2,,1,,1,-, +1031,32,10000,1,-1,-,1412103600,4096483201,ic0031,10,2,,1,,1,-, +1032,33,10000,1,-1,-,1412103600,4096483201,ic0032,10,2,,1,,1,-, +1033,34,10000,1,-1,-,1412103600,4096483201,ic0033,10,2,,1,,1,-, +1034,35,10000,1,-1,-,1412103600,4096483201,ic0034,10,2,,1,,1,-, +1035,36,10000,1,-1,-,1412103600,4096483201,ic0035,10,2,,1,,1,-, +1036,37,10000,1,-1,-,1412103600,4096483201,ic0036,10,2,,1,,1,-, +1037,38,10000,1,-1,-,1412103600,4096483201,ic0037,10,2,,1,,1,-, +1038,39,10000,1,-1,-,1412103600,4096483201,ic0038,10,2,,1,,1,-, +1039,40,10000,1,-1,-,1412103600,4096483201,ic0039,10,2,,1,,1,-, +1040,41,10000,1,-1,-,1412103600,4096483201,ic0040,10,2,,1,,1,-, +1041,42,10000,1,-1,-,1412103600,4096483201,ic0042,10,2,,1,,1,-, +1042,43,10000,1,-1,-,1412103600,4096483201,ic0043,10,2,,1,,1,-, +1043,44,10000,1,-1,-,1412103600,4096483201,ic0044,10,2,,1,,1,-, +1044,45,10000,1,-1,-,1412103600,4096483201,ic0045,10,2,,1,,1,-, +1045,46,10000,1,-1,-,1412103600,4096483201,ic0046,10,2,,1,,1,-, +1046,47,10000,1,-1,-,1412103600,4096483201,ic0047,10,2,,1,,1,-, +1047,48,10000,1,-1,-,1412103600,4096483201,ic0048,10,2,,1,,1,-, +1048,49,10000,1,-1,-,1412103600,4096483201,ic0049,10,2,,1,,1,-, +1049,50,10000,1,-1,-,1412103600,4096483201,ic0050,10,2,,1,,1,-, +1050,51,10000,1,-1,-,1412103600,4096483201,ic0051,10,2,,1,,1,-, +1051,52,10000,1,-1,-,1412103600,4096483201,ic0052,10,2,,1,,1,-, +1052,53,10000,1,-1,-,1412103600,4096483201,ic0053,10,2,,1,,1,-, +1053,54,10000,1,-1,-,1412103600,4096483201,ic0054,10,2,,1,,1,-, +1054,55,10000,1,-1,-,1412103600,4096483201,ic0055,10,2,,1,,1,-, +1055,56,10000,1,-1,-,1412103600,4096483201,ic0056,10,2,,1,,1,-, +1056,57,10000,1,-1,-,1412103600,4096483201,ic0057,10,2,,1,,1,-, +1057,58,10000,1,-1,-,1412103600,4096483201,ic0058,10,2,,1,,1,-, +1058,59,10000,1,-1,-,1412103600,4096483201,ic0059,10,2,,1,,1,-, +1059,60,10000,1,-1,-,1412103600,4096483201,ic0060,10,2,,1,,1,-, +1060,61,10000,1,-1,-,1412103600,4096483201,ic0061,10,2,,1,,1,-, +1061,62,10000,1,-1,-,1412103600,4096483201,ic0062,10,2,,1,,1,-, +1062,63,10000,1,-1,-,1412103600,4096483201,ic0063,10,2,,1,,1,-, +1063,64,10000,1,-1,-,1412103600,4096483201,ic0065,10,2,,1,,1,-, +1064,65,10000,1,-1,-,1412103600,4096483201,ic0066,10,2,,1,,1,-, +1065,66,10000,1,-1,-,1412103600,4096483201,ic0067,10,2,,1,,1,-, +1066,67,10000,1,-1,-,1412103600,4096483201,ic0069,10,2,,1,,1,-, +1067,68,10000,1,-1,-,1412103600,4096483201,ic0070,10,2,,1,,1,-, +1068,69,10000,1,-1,-,1412103600,4096483201,ic0071,10,2,,1,,1,-, +1069,70,10000,1,-1,-,1412103600,4096483201,ic0072,10,2,,1,,1,-, +1070,71,10000,1,-1,-,1412103600,4096483201,ic0074,10,2,,1,,1,-, +1071,72,10000,1,-1,-,1412103600,4096483201,ic0075,10,2,,1,,1,-, +1072,73,10000,1,-1,-,1412103600,4096483201,ic0076,10,2,,1,,1,-, +1073,74,10000,1,-1,-,1412103600,4096483201,ic0077,10,2,,1,,1,-, +1074,75,10000,1,-1,-,1412103600,4096483201,ic0078,10,2,,1,,1,-, +1075,76,10000,1,-1,-,1412103600,4096483201,ic0079,10,2,,1,,1,-, +1076,77,10000,1,-1,-,1412103600,4096483201,ic0080,10,2,,1,,1,-, +1077,78,10000,1,-1,-,1412103600,4096483201,ic0081,10,2,,1,,1,-, +1078,79,10000,1,-1,-,1412103600,4096483201,ic0082,10,2,,1,,1,-, +1079,80,10000,1,-1,-,1412103600,4096483201,ic0083,10,2,,1,,1,-, +1080,81,10000,1,-1,-,1412103600,4096483201,ic0084,10,2,,1,,1,-, +1081,82,10000,1,-1,-,1412103600,4096483201,ic0085,10,2,,1,,1,-, +1082,83,10000,1,-1,-,1412103600,4096483201,ic0086,10,2,,1,,1,-, +1083,84,10000,1,-1,-,1412103600,4096483201,ic0087,10,2,,1,,1,-, +1084,85,10000,1,-1,-,1412103600,4096483201,ic0088,10,2,,1,,1,-, +1085,86,10000,1,-1,-,1412103600,4096483201,ic0089,10,2,,1,,1,-, +1086,87,10000,1,-1,-,1412103600,4096483201,ic0090,10,2,,1,,1,-, +1087,88,10000,1,-1,-,1412103600,4096483201,ic0091,10,2,,1,,1,-, +1088,89,10000,1,-1,-,1412103600,4096483201,ic0092,10,2,,1,,1,-, +1089,90,10000,1,-1,-,1412103600,4096483201,ic0093,10,2,,1,,1,-, +1090,91,10000,1,-1,-,1412103600,4096483201,ic0094,10,2,,1,,1,-, +1091,92,10000,1,-1,-,1412103600,4096483201,ic0095,10,2,,1,,1,-, +1092,93,10000,1,-1,-,1412103600,4096483201,ic0096,10,2,,1,,1,-, +1093,94,10000,1,-1,-,1412103600,4096483201,ic0097,10,2,,1,,1,-, +1094,95,10000,1,-1,-,1412103600,4096483201,ic0098,10,2,,1,,1,-, +1095,96,10000,1,-1,-,1412103600,4096483201,ic0099,10,2,,1,,1,-, +1096,97,10000,1,-1,-,1412103600,4096483201,ic0100,10,2,,1,,1,-, +1097,98,10000,1,-1,-,1412103600,4096483201,ic0101,10,2,,1,,1,-, +1098,99,10000,1,-1,-,1412103600,4096483201,ic0102,10,2,,1,,1,-, +1099,100,10000,1,-1,-,1412103600,4096483201,ic0103,10,2,,1,,1,-, +1100,101,10000,1,-1,-,1412103600,4096483201,ic0104,10,2,,1,,1,-, +1101,102,10000,1,-1,-,1412103600,4096483201,ic0107,10,2,,1,,1,-, +1102,103,10000,1,-1,-,1412103600,4096483201,ic0108,10,2,,1,,1,-, +1103,104,10000,1,-1,-,1412103600,4096483201,ic0109,10,2,,1,,1,-, +1104,105,10000,1,-1,-,1412103600,4096483201,ic0110,10,2,,1,,1,-, +1105,106,10000,1,-1,-,1412103600,4096483201,ic0111,10,2,,1,,1,-, +1106,107,10000,1,-1,-,1412103600,4096483201,ic0112,10,2,,1,,1,-, +1107,108,10000,1,-1,-,1412103600,4096483201,ic0113,10,2,,1,,1,-, +1108,109,10000,1,-1,-,1412103600,4096483201,ic0114,10,2,,1,,1,-, +1109,110,10000,1,-1,-,1412103600,4096483201,ic0115,10,2,,1,,1,-, +1110,111,10000,1,-1,-,1412103600,4096483201,ic0116,10,2,,1,,1,-, +1111,112,10000,1,-1,-,1412103600,4096483201,ic0117,10,2,,1,,1,-, +1112,113,10000,1,-1,-,1412103600,4096483201,ic0118,10,2,,1,,1,-, +1113,114,10000,1,-1,-,1412103600,4096483201,ic0119,10,2,,1,,1,-, +1114,115,10000,1,-1,-,1412103600,4096483201,ic0120,10,2,,1,,1,-, +1115,116,10000,1,-1,-,1412103600,4096483201,ic0121,10,2,,1,,1,-, +1116,117,10000,1,-1,-,1412103600,4096483201,ic0122,10,2,,1,,1,-, +1117,118,10000,1,-1,-,1412103600,4096483201,ic0123,10,2,,1,,1,-, +1118,119,10000,1,-1,-,1412103600,4096483201,ic0124,10,2,,1,,1,-, +1119,120,10000,1,-1,-,1412103600,4096483201,ic0125,10,2,,1,,1,-, +1120,121,10000,1,-1,-,1412103600,4096483201,ic0126,10,2,,1,,1,-, +1121,122,10000,1,-1,-,1412103600,4096483201,ic0127,10,2,,1,,1,-, +1122,123,10000,1,-1,-,1412103600,4096483201,ic0128,10,2,,1,,1,-, +1123,124,10000,1,-1,-,1412103600,4096483201,ic0129,10,2,,1,,1,-, +1124,125,10000,1,-1,-,1412103600,4096483201,ic0130,10,2,,1,,1,-, +1125,126,10000,1,-1,-,1412103600,4096483201,ic0131,10,2,,1,,1,-, +1126,127,10000,1,-1,-,1412103600,4096483201,ic0132,10,2,,1,,1,-, +1127,128,10000,1,-1,-,1412103600,4096483201,ic0133,10,2,,1,,1,-, +1128,129,10000,1,-1,-,1412103600,4096483201,ic0134,10,2,,1,,1,-, +1129,130,10000,1,-1,-,1412103600,4096483201,ic0135,10,2,,1,,1,-, +1130,131,10000,1,-1,-,1412103600,4096483201,ic0137,10,2,,1,,1,-, +1131,132,10000,1,-1,-,1412103600,4096483201,ic0138,10,2,,1,,1,-, +1132,133,10000,1,-1,-,1412103600,4096483201,ic0139,10,2,,1,,1,-, +1133,134,10000,1,-1,-,1412103600,4096483201,ic0140,10,2,,1,,1,-, +1134,135,10000,1,-1,-,1412103600,4096483201,ic0141,10,2,,1,,1,-, +1135,136,10000,1,-1,-,1412103600,4096483201,ic0143,10,2,,1,,1,-, +1136,137,10000,1,-1,-,1412103600,4096483201,ic0144,10,2,,1,,1,-, +1137,138,10000,1,-1,-,1412103600,4096483201,ic0145,10,2,,1,,1,-, +1138,139,10000,1,-1,-,1412103600,4096483201,ic0146,10,2,,1,,1,-, +1139,140,10000,1,-1,-,1412103600,4096483201,ic0147,10,2,,1,,1,-, +1140,141,10000,1,-1,-,1412103600,4096483201,ic0148,10,2,,1,,1,-, +1141,142,10000,1,-1,-,1412103600,4096483201,ic0149,10,2,,1,,1,-, +1142,143,10000,1,-1,-,1412103600,4096483201,ic0150,10,2,,1,,1,-, +1143,144,10000,1,-1,-,1412103600,4096483201,ic0151,10,2,,1,,1,-, +1144,145,10000,1,-1,-,1412103600,4096483201,ic0152,10,2,,1,,1,-, +1145,146,10000,1,-1,-,1412103600,4096483201,ic0153,10,2,,1,,1,-, +1146,147,10000,1,-1,-,1412103600,4096483201,ic0154,10,2,,1,,1,-, +1147,148,10000,1,-1,-,1412103600,4096483201,ic0155,10,2,,1,,1,-, +1148,149,10000,1,-1,-,1412103600,4096483201,ic0156,10,2,,1,,1,-, +1149,150,10000,1,-1,-,1412103600,4096483201,ic0157,10,2,,1,,1,-, +1150,151,10000,1,-1,-,1412103600,4096483201,ic0158,10,2,,1,,1,-, +1151,152,10000,1,-1,-,1412103600,4096483201,ic0159,10,2,,1,,1,-, +1152,153,10000,1,-1,-,1412103600,4096483201,ic0160,10,2,,1,,1,-, +1153,154,10000,1,-1,-,1412103600,4096483201,ic0161,10,2,,1,,1,-, +1154,155,10000,1,-1,-,1412103600,4096483201,ic0162,10,2,,1,,1,-, +1155,156,10000,1,-1,-,1412103600,4096483201,ic0163,10,2,,1,,1,-, +1156,157,10000,1,-1,-,1412103600,4096483201,ic0164,10,2,,1,,1,-, +1157,158,10000,1,-1,-,1412103600,4096483201,ic0165,10,2,,1,,1,-, +1158,159,10000,1,-1,-,1412103600,4096483201,ic0166,10,2,,1,,1,-, +1159,160,10000,1,-1,-,1412103600,4096483201,ic0167,10,2,,1,,1,-, +1160,161,10000,1,-1,-,1412103600,4096483201,ic0168,10,2,,1,,1,-, +1161,162,10000,1,-1,-,1412103600,4096483201,ic0169,10,2,,1,,1,-, +1162,163,10000,1,-1,-,1412103600,4096483201,ic0170,10,2,,1,,1,-, +1163,164,10000,1,-1,-,1412103600,4096483201,ic0171,10,2,,1,,1,-, +1164,165,10000,1,-1,-,1412103600,4096483201,ic0172,10,2,,1,,1,-, +1165,166,10000,1,-1,-,1412103600,4096483201,ic0173,10,2,,1,,1,-, +1166,167,10000,1,-1,-,1412103600,4096483201,ic0174,10,2,,1,,1,-, +1167,168,10000,1,-1,-,1412103600,4096483201,ic0175,10,2,,1,,1,-, +1168,169,10000,1,-1,-,1412103600,4096483201,ic0178,10,2,,1,,1,-, +1169,170,10000,1,-1,-,1412103600,4096483201,ic0179,10,2,,1,,1,-, +1170,171,10000,1,-1,-,1412103600,4096483201,ic0180,10,2,,1,,1,-, +1171,172,10000,1,-1,-,1412103600,4096483201,ic0182,10,2,,1,,1,-, +1172,173,10000,1,-1,-,1412103600,4096483201,ic0183,10,2,,1,,1,-, +1173,174,10000,1,-1,-,1412103600,4096483201,ic0184,10,2,,1,,1,-, +1174,175,10000,1,-1,-,1412103600,4096483201,ic0185,10,2,,1,,1,-, +1175,176,10000,1,-1,-,1412103600,4096483201,ic0186,10,2,,1,,1,-, +1176,177,10000,1,-1,-,1412103600,4096483201,ic0187,10,2,,1,,1,-, +1177,178,10000,1,-1,-,1412103600,4096483201,ic0188,10,2,,1,,1,-, +1178,179,10000,1,-1,-,1412103600,4096483201,ic0189,10,2,,1,,1,-, +1179,180,10000,1,-1,-,1412103600,4096483201,ic0190,10,2,,1,,1,-, +1180,181,10000,1,-1,-,1412103600,4096483201,ic0191,10,2,,1,,1,-, +1181,182,10000,1,-1,-,1412103600,4096483201,ic0192,10,2,,1,,1,-, +1182,183,10000,1,-1,-,1412103600,4096483201,ic0193,10,2,,1,,1,-, +1183,184,10000,1,-1,-,1412103600,4096483201,ic0194,10,2,,1,,1,-, +1184,185,10000,1,-1,-,1412103600,4096483201,ic0195,10,2,,1,,1,-, +1185,186,10000,1,-1,-,1412103600,4096483201,ic0196,10,2,,1,,1,-, +1186,187,10000,1,-1,-,1412103600,4096483201,ic0198,10,2,,1,,1,-, +1187,188,10000,1,-1,-,1412103600,4096483201,ic0199,10,2,,1,,1,-, +1188,189,10000,1,-1,-,1412103600,4096483201,ic0200,10,2,,1,,1,-, +1189,190,10000,1,-1,-,1412103600,4096483201,ic0201,10,2,,1,,1,-, +1190,191,10000,1,-1,-,1412103600,4096483201,ic0202,10,2,,1,,1,-, +1191,192,10000,1,-1,-,1412103600,4096483201,ic0203,10,2,,1,,1,-, +1192,193,10000,1,-1,-,1412103600,4096483201,ic0204,10,2,,1,,1,-, +1193,194,10000,1,-1,-,1412103600,4096483201,ic0205,10,2,,1,,1,-, +1194,195,10000,1,-1,-,1412103600,4096483201,ic0206,10,2,,1,,1,-, +1195,196,10000,1,-1,-,1412103600,4096483201,ic0207,10,2,,1,,1,-, +1196,197,10000,1,-1,-,1412103600,4096483201,ic0208,10,2,,1,,1,-, +1197,198,10000,1,-1,-,1412103600,4096483201,ic0209,10,2,,1,,1,-, +1198,199,10000,1,-1,-,1412103600,4096483201,ic0210,10,2,,1,,1,-, +1199,200,10000,1,-1,-,1412103600,4096483201,ic0211,10,2,,1,,1,-, +1200,201,10000,1,-1,-,1412103600,4096483201,ic0212,10,2,,1,,1,-, +1201,202,10000,1,-1,-,1412103600,4096483201,ic0213,10,2,,1,,1,-, +1202,203,10000,1,-1,-,1412103600,4096483201,ic0214,10,2,,1,,1,-, +1203,204,10000,1,-1,-,1412103600,4096483201,ic0215,10,2,,1,,1,-, +1204,205,10000,1,-1,-,1412103600,4096483201,ic0216,10,2,,1,,1,-, +1205,206,10000,1,-1,-,1412103600,4096483201,ic0217,10,2,,1,,1,-, +1206,207,10000,1,-1,-,1412103600,4096483201,ic0218,10,2,,1,,1,-, +1207,208,10000,1,-1,-,1412103600,4096483201,ic0219,10,2,,1,,1,-, +1208,209,10000,1,-1,-,1412103600,4096483201,ic0220,10,2,,1,,1,-, +1209,210,10000,1,-1,-,1412103600,4096483201,ic0221,10,2,,1,,1,-, +1210,211,10000,1,-1,-,1412103600,4096483201,ic0222,10,2,,1,,1,-, +1211,212,10000,1,-1,-,1412103600,4096483201,ic0223,10,2,,1,,1,-, +1212,213,10000,1,-1,-,1412103600,4096483201,ic0224,10,2,,1,,1,-, +1213,214,10000,1,-1,-,1412103600,4096483201,ic0225,10,2,,1,,1,-, +1214,215,10000,1,-1,-,1412103600,4096483201,ic0226,10,2,,1,,1,-, +1215,216,10000,1,-1,-,1412103600,4096483201,ic0227,10,2,,1,,1,-, +1216,217,10000,1,-1,-,1412103600,4096483201,ic0228,10,2,,1,,1,-, +1217,218,10000,1,-1,-,1412103600,4096483201,ic0229,10,2,,1,,1,-, +1218,219,10000,1,-1,-,1412103600,4096483201,ic0230,10,2,,1,,1,-, +1219,220,10000,1,-1,-,1412103600,4096483201,ic0231,10,2,,1,,1,-, +1220,221,10000,1,-1,-,1412103600,4096483201,ic0232,10,2,,1,,1,-, +1221,222,10000,1,-1,-,1412103600,4096483201,ic0233,10,2,,1,,1,-, +1222,223,10000,1,-1,-,1412103600,4096483201,ic0234,10,2,,1,,1,-, +1223,224,10000,1,-1,-,1412103600,4096483201,ic0235,10,2,,1,,1,-, +1224,225,10000,1,-1,-,1412103600,4096483201,ic0236,10,2,,1,,1,-, +1225,226,10000,1,-1,-,1412103600,4096483201,ic0237,10,2,,1,,1,-, +1226,227,10000,1,-1,-,1412103600,4096483201,ic0239,10,2,,1,,1,-, +1227,228,10000,1,-1,-,1412103600,4096483201,ic0240,10,2,,1,,1,-, +1228,229,10000,1,-1,-,1412103600,4096483201,ic0241,10,2,,1,,1,-, +1229,230,10000,1,-1,-,1412103600,4096483201,ic0242,10,2,,1,,1,-, +1230,231,10000,1,-1,-,1412103600,4096483201,ic0243,10,2,,1,,1,-, +1231,232,10000,1,-1,-,1412103600,4096483201,ic0244,10,2,,1,,1,-, +1232,233,10000,1,-1,-,1412103600,4096483201,ic0245,10,2,,1,,1,-, +1233,234,10000,1,-1,-,1412103600,4096483201,ic0246,10,2,,1,,1,-, +1234,235,10000,1,-1,-,1412103600,4096483201,ic0249,10,2,,1,,1,-, +1235,236,10000,1,-1,-,1412103600,4096483201,ic0250,10,2,,1,,1,-, +1236,237,10000,1,-1,-,1412103600,4096483201,ic0251,10,2,,1,,1,-, +1237,238,10000,1,-1,-,1412103600,4096483201,ic0252,10,2,,1,,1,-, +1238,239,10000,1,-1,-,1412103600,4096483201,ic0253,10,2,,1,,1,-, +1239,240,10000,1,-1,-,1412103600,4096483201,ic0254,10,2,,1,,1,-, +1240,241,10000,1,-1,-,1412103600,4096483201,ic0255,10,2,,1,,1,-, +1241,242,10000,1,-1,-,1412103600,4096483201,ic0256,10,2,,1,,1,-, +1242,243,10000,1,-1,-,1412103600,4096483201,ic0257,10,2,,1,,1,-, +1243,244,10000,1,-1,-,1412103600,4096483201,ic0258,10,2,,1,,1,-, +1244,245,10000,1,-1,-,1412103600,4096483201,ic0259,10,2,,1,,1,-, +1245,246,10000,1,-1,-,1412103600,4096483201,ic0260,10,2,,1,,1,-, +1246,247,10000,1,-1,-,1412103600,4096483201,ic0261,10,2,,1,,1,-, +1247,248,10000,1,-1,-,1412103600,4096483201,ic0262,10,2,,1,,1,-, +1248,249,10000,1,-1,-,1412103600,4096483201,ic0263,10,2,,1,,1,-, +1249,250,10000,1,-1,-,1412103600,4096483201,ic0265,10,2,,1,,1,-, +1250,251,10000,1,-1,-,1412103600,4096483201,ic0266,10,2,,1,,1,-, +1251,252,10000,1,-1,-,1412103600,4096483201,ic0267,10,2,,1,,1,-, +1252,253,10000,1,-1,-,1412103600,4096483201,ic0268,10,2,,1,,1,-, +1253,254,10000,1,-1,-,1412103600,4096483201,ic0269,10,2,,1,,1,-, +1254,255,10000,1,-1,-,1412103600,4096483201,ic0270,10,2,,1,,1,-, +1255,256,10000,1,-1,-,1412103600,4096483201,ic0271,10,2,,1,,1,-, +1256,257,10000,1,-1,-,1412103600,4096483201,ic0272,10,2,,1,,1,-, +1257,258,10000,1,-1,-,1412103600,4096483201,ic0273,10,2,,1,,1,-, +1258,259,10000,1,-1,-,1412103600,4096483201,ic0274,10,2,,1,,1,-, +1259,260,10000,1,-1,-,1412103600,4096483201,ic0276,10,2,,1,,1,-, +1260,261,10000,1,-1,-,1412103600,4096483201,ic0277,10,2,,1,,1,-, +1261,262,10000,1,-1,-,1412103600,4096483201,ic0278,10,2,,1,,1,-, +1262,263,10000,1,-1,-,1412103600,4096483201,ic0279,10,2,,1,,1,-, +1263,264,10000,1,-1,-,1412103600,4096483201,ic0280,10,2,,1,,1,-, +1264,265,10000,1,-1,-,1412103600,4096483201,ic0281,10,2,,1,,1,-, +1265,266,10000,1,-1,-,1412103600,4096483201,ic0282,10,2,,1,,1,-, +1266,267,10000,1,-1,-,1412103600,4096483201,ic0283,10,2,,1,,1,-, +1267,268,10000,1,-1,-,1412103600,4096483201,ic0284,10,2,,1,,1,-, +1268,269,10000,1,-1,-,1412103600,4096483201,ic0286,10,2,,1,,1,-, +1269,270,10000,1,-1,-,1412103600,4096483201,ic0287,10,2,,1,,1,-, +1270,271,10000,1,-1,-,1412103600,4096483201,ic0288,10,2,,1,,1,-, +1271,272,10000,1,-1,-,1412103600,4096483201,ic0289,10,2,,1,,1,-, +1272,273,10000,1,-1,-,1412103600,4096483201,ic0290,10,2,,1,,1,-, +1273,274,10000,1,-1,-,1412103600,4096483201,ic0291,10,2,,1,,1,-, +1274,275,10000,1,-1,-,1412103600,4096483201,ic0292,10,2,,1,,1,-, +1275,276,10000,1,-1,-,1412103600,4096483201,ic0293,10,2,,1,,1,-, +1276,277,10000,1,-1,-,1412103600,4096483201,ic0294,10,2,,1,,1,-, +1277,278,10000,1,-1,-,1412103600,4096483201,ic0295,10,2,,1,,1,-, +1278,279,10000,1,-1,-,1412103600,4096483201,ic0296,10,2,,1,,1,-, +1279,280,10000,1,-1,-,1412103600,4096483201,ic0297,10,2,,1,,1,-, +1280,281,10000,1,-1,-,1412103600,4096483201,ic0298,10,2,,1,,1,-, +1281,282,10000,1,-1,-,1412103600,4096483201,ic0299,10,2,,1,,1,-, +1282,283,10000,1,-1,-,1412103600,4096483201,ic0300,10,2,,1,,1,-, +1283,284,10000,1,-1,-,1412103600,4096483201,ic0301,10,2,,1,,1,-, +1284,285,10000,1,-1,-,1412103600,4096483201,ic0302,10,2,,1,,1,-, +1285,286,10000,1,-1,-,1412103600,4096483201,ic1000,10,2,,1,,1,-, +1286,287,10000,1,-1,-,1412103600,4096483201,ic1001,10,2,,1,,1,-, +1287,288,10000,1,-1,-,1412103600,4096483201,ic1002,10,2,,1,,1,-, +1288,289,10000,1,-1,-,1412103600,4096483201,ic1004,10,2,,1,,1,-, +1289,290,10000,1,-1,-,1412103600,4096483201,ic1005,10,2,,1,,1,-, +1290,291,10000,1,-1,-,1412103600,4096483201,ic1006,10,2,,1,,1,-, +1291,292,10000,1,-1,-,1412103600,4096483201,ic1007,10,2,,1,,1,-, +1292,293,10000,1,-1,-,1412103600,4096483201,ic1008,10,2,,1,,1,-, +1293,294,10000,1,-1,-,1412103600,4096483201,ic1010,10,2,,1,,1,-, +1294,295,10000,1,-1,-,1412103600,4096483201,ic1011,10,2,,1,,1,-, +1295,296,10000,1,-1,-,1412103600,4096483201,ic1012,10,2,,1,,1,-, +1296,297,10000,1,-1,-,1412103600,4096483201,ic1013,10,2,,1,,1,-, +1297,298,10000,1,-1,-,1412103600,4096483201,ic1014,10,2,,1,,1,-, +1298,299,10000,1,-1,-,1412103600,4096483201,ic1015,10,2,,1,,1,-, +1299,300,10000,1,-1,-,1412103600,4096483201,ic1016,10,2,,1,,1,-, +1300,301,10000,1,-1,-,1412103600,4096483201,ic1017,10,2,,1,,1,-, +1301,302,10000,1,-1,-,1412103600,4096483201,ic1018,10,2,,1,,1,-, +1302,303,10000,1,-1,-,1412103600,4096483201,ic1020,10,2,,1,,1,-, +1303,304,10000,1,-1,-,1412103600,4096483201,ic1021,10,2,,1,,1,-, +1304,305,10000,1,-1,-,1412103600,4096483201,ic1022,10,2,,1,,1,-, +1305,306,10000,1,-1,-,1412103600,4096483201,ic1023,10,2,,1,,1,-, +1306,307,10000,1,-1,-,1412103600,4096483201,ic1024,10,2,,1,,1,-, +1307,308,10000,1,-1,-,1412103600,4096483201,ic1025,10,2,,1,,1,-, +1308,309,10000,1,-1,-,1412103600,4096483201,ic1026,10,2,,1,,1,-, +1309,310,10000,1,-1,-,1412103600,4096483201,ic1027,10,2,,1,,1,-, +1310,311,10000,1,-1,-,1412103600,4096483201,ic1028,10,2,,1,,1,-, +1311,312,10000,1,-1,-,1412103600,4096483201,ic1029,10,2,,1,,1,-, +1312,313,10000,1,-1,-,1412103600,4096483201,ic1030,10,2,,1,,1,-, +1313,314,10000,1,-1,-,1412103600,4096483201,ic1031,10,2,,1,,1,-, +1314,315,10000,1,-1,-,1412103600,4096483201,ic1032,10,2,,1,,1,-, +1315,316,10000,1,-1,-,1412103600,4096483201,ic1033,10,2,,1,,1,-, +1316,317,10000,1,-1,-,1412103600,4096483201,ic1034,10,2,,1,,1,-, +1317,318,10000,1,-1,-,1412103600,4096483201,ic1035,10,2,,1,,1,-, +1318,319,10000,1,-1,-,1412103600,4096483201,ic1036,10,2,,1,,1,-, +1319,320,10000,1,-1,-,1412103600,4096483201,ic1037,10,2,,1,,1,-, +1320,321,10000,1,-1,-,1412103600,4096483201,ic1038,10,2,,1,,1,-, +1321,322,10000,1,-1,-,1412103600,4096483201,ic1039,10,2,,1,,1,-, +1322,323,10000,1,-1,-,1412103600,4096483201,ic1040,10,2,,1,,1,-, +1323,324,10000,1,-1,-,1412103600,4096483201,ic1041,10,2,,1,,1,-, +1324,325,10000,1,-1,-,1412103600,4096483201,ic1042,10,2,,1,,1,-, +1325,326,10000,1,-1,-,1412103600,4096483201,ic1043,10,2,,1,,1,-, +1326,327,10000,1,-1,-,1412103600,4096483201,ic1044,10,2,,1,,1,-, +1327,328,10000,1,-1,-,1412103600,4096483201,ic1045,10,2,,1,,1,-, +1328,329,10000,1,-1,-,1412103600,4096483201,ic1046,10,2,,1,,1,-, +1329,330,10000,1,-1,-,1412103600,4096483201,ic1047,10,2,,1,,1,-, +1330,331,10000,1,-1,-,1412103600,4096483201,ic1048,10,2,,1,,1,-, +1331,332,10000,1,-1,-,1412103600,4096483201,ic1049,10,2,,1,,1,-, +1332,333,10000,1,-1,-,1412103600,4096483201,ic1050,10,2,,1,,1,-, +1333,334,10000,1,-1,-,1412103600,4096483201,ic1051,10,2,,1,,1,-, +1334,335,10000,1,-1,-,1412103600,4096483201,ic1052,10,2,,1,,1,-, +1335,336,10000,1,-1,-,1412103600,4096483201,ic1053,10,2,,1,,1,-, +1336,337,10000,1,-1,-,1412103600,4096483201,ic1054,10,2,,1,,1,-, +1337,338,10000,1,-1,-,1412103600,4096483201,ic1055,10,2,,1,,1,-, +1338,339,10000,1,-1,-,1412103600,4096483201,ic1056,10,2,,1,,1,-, +1339,340,10000,1,-1,-,1412103600,4096483201,ic1057,10,2,,1,,1,-, +1340,341,10000,1,-1,-,1412103600,4096483201,ic1058,10,2,,1,,1,-, +1341,342,10000,1,-1,-,1412103600,4096483201,ic1059,10,2,,1,,1,-, +1342,343,10000,1,-1,-,1412103600,4096483201,ic1060,10,2,,1,,1,-, +1343,344,10000,1,-1,-,1412103600,4096483201,ic1061,10,2,,1,,1,-, +1344,345,10000,1,-1,-,1412103600,4096483201,ic1062,10,2,,1,,1,-, +1345,346,10000,1,-1,-,1412103600,4096483201,ic1063,10,2,,1,,1,-, +1346,347,10000,1,-1,-,1412103600,4096483201,ic1064,10,2,,1,,1,-, +1347,348,10000,1,-1,-,1412103600,4096483201,ic1065,10,2,,1,,1,-, +1348,349,10000,1,-1,-,1412103600,4096483201,ic1066,10,2,,1,,1,-, +1349,350,10000,1,-1,-,1412103600,4096483201,ic1067,10,2,,1,,1,-, +1350,351,10000,1,-1,-,1412103600,4096483201,ic1068,10,2,,1,,1,-, +1351,352,10000,1,-1,-,1412103600,4096483201,ic1069,10,2,,1,,1,-, +1352,353,10000,1,-1,-,1412103600,4096483201,ic1070,10,2,,1,,1,-, +1353,354,10000,1,-1,-,1412103600,4096483201,ic1071,10,2,,1,,1,-, +1354,355,10000,1,-1,-,1412103600,4096483201,ic1072,10,2,,1,,1,-, +1355,356,10000,1,-1,-,1412103600,4096483201,ic1073,10,2,,1,,1,-, +1356,357,10000,1,-1,-,1412103600,4096483201,ic1500,10,2,,1,,1,-, +1357,358,10000,1,-1,-,1412103600,4096483201,ic1501,10,2,,1,,1,-, +1358,359,10000,1,-1,-,1412103600,4096483201,ic1502,10,2,,1,,1,-, +1359,360,10000,1,-1,-,1412103600,4096483201,ic1503,10,2,,1,,1,-, +1360,361,10000,1,-1,-,1412103600,4096483201,ic1504,10,2,,1,,1,-, +1361,362,10000,1,-1,-,1412103600,4096483201,ic1505,10,2,,1,,1,-, +1362,363,10000,1,-1,-,1412103600,4096483201,ic1506,10,2,,1,,1,-, +1363,364,10000,1,-1,-,1412103600,4096483201,ic1507,10,2,,1,,1,-, +1364,365,10000,1,-1,-,1412103600,4096483201,ic1508,10,2,,1,,1,-, +1365,366,10000,1,-1,-,1412103600,4096483201,ic1509,10,2,,1,,1,-, +1366,367,10000,1,-1,-,1412103600,4096483201,ic1510,10,2,,1,,1,-, +1367,368,10000,1,-1,-,1412103600,4096483201,ic1511,10,2,,1,,1,-, +1368,369,10000,1,-1,-,1412103600,4096483201,ic1512,10,2,,1,,1,-, +1369,370,10000,1,-1,-,1412103600,4096483201,ic1513,10,2,,1,,1,-, +1370,371,10000,1,-1,-,1412103600,4096483201,ic1514,10,2,,1,,1,-, +1371,372,10000,1,-1,-,1412103600,4096483201,ic1515,10,2,,1,,1,-, +1372,373,10000,1,-1,-,1412103600,4096483201,ic1516,10,2,,1,,1,-, +1373,374,10000,1,-1,-,1412103600,4096483201,ic1517,10,2,,1,,1,-, +1374,375,10000,1,-1,-,1412103600,4096483201,ic1518,10,2,,1,,1,-, +1375,376,10000,1,-1,-,1412103600,4096483201,ic1519,10,2,,1,,1,-, +1376,377,10000,1,-1,-,1412103600,4096483201,ic1520,10,2,,1,,1,-, +1377,378,10000,1,-1,-,1412103600,4096483201,ic1521,10,2,,1,,1,-, +1378,379,10000,1,-1,-,1412103600,4096483201,ic1522,10,2,,1,,1,-, +1379,380,10000,1,-1,-,1412103600,4096483201,ic1523,10,2,,1,,1,-, +1380,381,10000,1,-1,-,1412103600,4096483201,ic1524,10,2,,1,,1,-, +1381,382,10000,1,-1,-,1412103600,4096483201,ic1525,10,2,,1,,1,-, +1382,383,10000,1,-1,-,1412103600,4096483201,ic1526,10,2,,1,,1,-, +1383,384,10000,1,-1,-,1412103600,4096483201,ic1527,10,2,,1,,1,-, +1384,385,10000,1,-1,-,1412103600,4096483201,ic1528,10,2,,1,,1,-, +1385,386,10000,1,-1,-,1412103600,4096483201,ic1529,10,2,,1,,1,-, +1386,387,10000,1,-1,-,1412103600,4096483201,ic1530,10,2,,1,,1,-, +1387,388,10000,1,-1,-,1412103600,4096483201,ic1531,10,2,,1,,1,-, +1388,389,10000,1,-1,-,1412103600,4096483201,ic1532,10,2,,1,,1,-, +1389,390,10000,1,-1,-,1412103600,4096483201,ic1533,10,2,,1,,1,-, +1390,391,10000,1,-1,-,1412103600,4096483201,ic1534,10,2,,1,,1,-, +1391,392,10000,1,-1,-,1412103600,4096483201,ic1535,10,2,,1,,1,-, +1392,393,10000,1,-1,-,1412103600,4096483201,ic1536,10,2,,1,,1,-, +1393,394,10000,1,-1,-,1412103600,4096483201,ic1537,10,2,,1,,1,-, +1394,395,10000,1,-1,-,1412103600,4096483201,ic1538,10,2,,1,,1,-, +1395,396,10000,1,-1,-,1412103600,4096483201,ic1539,10,2,,1,,1,-, +1396,397,10000,1,-1,-,1412103600,4096483201,ic1540,10,2,,1,,1,-, +1397,398,10000,1,-1,-,1412103600,4096483201,ic1541,10,2,,1,,1,-, +1398,399,10000,1,-1,-,1412103600,4096483201,ic1542,10,2,,1,,1,-, +1399,400,10000,1,-1,-,1412103600,4096483201,ic1543,10,2,,1,,1,-, +1400,401,10000,1,-1,-,1412103600,4096483201,ic1544,10,2,,1,,1,-, +1401,402,10000,1,-1,-,1412103600,4096483201,ic1545,10,2,,1,,1,-, +1402,403,10000,1,-1,-,1412103600,4096483201,ic1546,10,2,,1,,1,-, +1403,404,10000,1,-1,-,1412103600,4096483201,ic1547,10,2,,1,,1,-, +1404,405,10000,1,-1,-,1412103600,4096483201,ic1550,10,2,,1,,1,-, +1405,406,10000,1,-1,-,1412103600,4096483201,ic1551,10,2,,1,,1,-, +1406,407,10000,1,-1,-,1412103600,4096483201,ic1552,10,2,,1,,1,-, +1407,408,10000,1,-1,-,1412103600,4096483201,ic1553,10,2,,1,,1,-, +1408,409,10000,1,-1,-,1412103600,4096483201,ic1554,10,2,,1,,1,-, +1409,410,10000,1,-1,-,1412103600,4096483201,ic1555,10,2,,1,,1,-, +1410,411,10000,1,-1,-,1412103600,4096483201,ic1556,10,2,,1,,1,-, +1411,412,10000,1,-1,-,1412103600,4096483201,ic1557,10,2,,1,,1,-, +1412,413,10000,1,-1,-,1412103600,4096483201,ic1558,10,2,,1,,1,-, +1413,414,10000,1,-1,-,1412103600,4096483201,ic1559,10,2,,1,,1,-, +1414,415,10000,1,-1,-,1412103600,4096483201,ic1560,10,2,,1,,1,-, +1415,416,10000,1,-1,-,1412103600,4096483201,ic1561,10,2,,1,,1,-, +1416,417,10000,1,-1,-,1412103600,4096483201,ic1562,10,2,,1,,1,-, +1417,418,10000,1,-1,-,1412103600,4096483201,ic1563,10,2,,1,,1,-, +1418,419,10000,1,-1,-,1412103600,4096483201,ic1600,10,2,,1,,1,-, +1419,420,10000,1,-1,-,1412103600,4096483201,ic1601,10,2,,1,,1,-, +1420,421,10000,1,-1,-,1412103600,4096483201,ic1602,10,2,,1,,1,-, +1421,422,10000,1,-1,-,1412103600,4096483201,ic1603,10,2,,1,,1,-, +1422,423,10000,1,-1,-,1412103600,4096483201,ic1604,10,2,,1,,1,-, +1423,424,10000,1,-1,-,1412103600,4096483201,ic1605,10,2,,1,,1,-, +1424,425,10000,1,-1,-,1412103600,4096483201,ic1606,10,2,,1,,1,-, +1425,426,10000,1,-1,-,1412103600,4096483201,ic1607,10,2,,1,,1,-, +1426,427,10000,1,-1,-,1412103600,4096483201,ic1608,10,2,,1,,1,-, +1427,428,10000,1,-1,-,1412103600,4096483201,ic1609,10,2,,1,,1,-, +1428,429,10000,1,-1,-,1412103600,4096483201,ic1610,10,2,,1,,1,-, +1429,430,10000,1,-1,-,1412103600,4096483201,ic1611,10,2,,1,,1,-, +1430,431,10000,1,-1,-,1412103600,4096483201,ic1612,10,2,,1,,1,-, +1431,432,10000,1,-1,-,1412103600,4096483201,ic1613,10,2,,1,,1,-, +1432,433,10000,1,-1,-,1412103600,4096483201,ic1614,10,2,,1,,1,-, +1433,434,10000,1,-1,-,1412103600,4096483201,ic1615,10,2,,1,,1,-, +1434,435,10000,1,-1,-,1412103600,4096483201,ic1616,10,2,,1,,1,-, +1435,436,10000,1,-1,-,1412103600,4096483201,ic1617,10,2,,1,,1,-, +1436,437,10000,1,-1,-,1412103600,4096483201,ic1618,10,2,,1,,1,-, +1437,438,10000,1,-1,-,1412103600,4096483201,ic1619,10,2,,1,,1,-, +1438,439,10000,1,-1,-,1412103600,4096483201,ic1620,10,2,,1,,1,-, +1439,440,10000,1,-1,-,1412103600,4096483201,ic1622,10,2,,1,,1,-, +1440,441,10000,1,-1,-,1412103600,4096483201,ic1623,10,2,,1,,1,-, +1441,442,10000,1,-1,-,1412103600,4096483201,ic1624,10,2,,1,,1,-, +1442,443,10000,1,-1,-,1412103600,4096483201,ic1625,10,2,,1,,1,-, +1443,444,10000,1,-1,-,1412103600,4096483201,ic1626,10,2,,1,,1,-, +1444,445,10000,1,-1,-,1412103600,4096483201,ic1700,10,2,,1,,1,-, +1445,446,10000,1,-1,-,1412103600,4096483201,ic1701,10,2,,1,,1,-, +1446,447,10000,1,-1,-,1412103600,4096483201,ic1702,10,2,,1,,1,-, +1447,448,10000,1,-1,-,1412103600,4096483201,ic1703,10,2,,1,,1,-, +1448,449,10000,1,-1,-,1412103600,4096483201,ic1704,10,2,,1,,1,-, +1449,450,10000,1,-1,-,1412103600,4096483201,ic1705,10,2,,1,,1,-, +1450,451,10000,1,-1,-,1412103600,4096483201,ic1706,10,2,,1,,1,-, +1451,452,10000,1,-1,-,1412103600,4096483201,ic1717,10,2,,1,,1,-, diff --git a/titles/cxb/rss2_data/Shop/ShopList_Music.csv b/titles/cxb/rss2_data/Shop/ShopList_Music.csv new file mode 100644 index 0000000..cf5127b --- /dev/null +++ b/titles/cxb/rss2_data/Shop/ShopList_Music.csv @@ -0,0 +1,13 @@ +shopID.,æ•´ç†ç”¨No.,Ver.,出ç¾ãƒ•ãƒ©ã‚°,出ç¾ãƒ•ãƒ©ã‚°å‚ç…§ID,æ¡ä»¶,出ç¾æ™‚é–“,消滅時間,MusicCode,価格,表示タイプ,Text,Type,Value(op),Value,対象曲,Difficulty(op),Difficulty,Level(op),Level,Grade(Op),Grade,GaugeType(op),GaugeType,HS(op),HS,APP,DAP,F-V,F-H,FullCombo,Combo(op),Combo,ClearRate(op),ClearRate,プレイ日,地域, +0,1,1.00.00,3,1,-,1411697520.0288,4096483201.0288,megaro,0,0,-,-1,-1,-1,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +1,2,1.00.00,1,-1,-,1411697520.0288,4096483201.0288,nature,10,2,???,-1,-1,-1,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +2,3,1.00.00,2,-1,-,1412103600.0288,4096483201.992,hopesb,30,1,「Landing on the moonã€ã‚’STANDARD以上ã§ã‚¯ãƒªã‚¢ã™ã‚‹ã€‚,0,-1,-1,landin,1,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +3,4,1.00.00,1,-1,-,1412103600.0288,4096483201.992,flower,10,0,-,-1,-1,-1,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +4,5,1.00.02,1,-1,1&2&3,1412103600.0288,4096483201.992,reseed3,10,0,「Human Natureã€ã€ŒHopes Brightã€ã€ŒFlowerwallã€ã‚’クリアã™ã‚‹ã€‚,-1,-1,-1,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +5,6,1.00.00,1,-1,-,1411697520.0288,4096483201.0288,dennou,20,1,-,0,-1,-1,flower,1,2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,2,-1,-1,-1,-1,,, +6,7,1.00.00,2,-1,5&7,1411697520.0288,4096483201.0288,romanc,10,1,-,-1,-1,-1,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +7,8,1.00.00,1,-1,-,1411697520.0288,4096483201.0288,landin,40,1,-,-1,-1,-1,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +8,9,1.00.00,1,-1,7,1411697520.0288,4096483201.0288,ididid,50,0,-,-1,-1,-1,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,2,-1,-1,-1,-1,,, +9,10,1.00.00,1,-1,-,1411697520.0288,4096483201.0288,crissc,60,0,MASTER以上ã®2曲S+以上フルコンボクリアã™ã‚‹ã€‚,1,1,2,-,1,2,-1,0,1,1,1,2,-1,-1,-1,-1,-1,-1,1,-1,-1,-1,-1,,, +10,11,1.00.00,1,-1,-,1411697520.0288,4096483201.0288,dazzli,70,1,MASTER以上ã®4曲S+以上クリアã™ã‚‹ã€‚,1,1,4,-,1,2,-1,0,1,1,1,2,-1,-1,-1,-1,-1,-1,2,-1,-1,-1,-1,,, +11,12,1.00.00,1,-1,-,1411697520.0288,4096483201.0288,izimed,987654,1,MASTER以上ã®7曲S+以上クリアã™ã‚‹ã€‚,1,1,7,-,1,2,-1,0,1,1,1,2,-1,-1,-1,-1,-1,-1,2,-1,-1,-1,-1,,, diff --git a/titles/cxb/rss2_data/Shop/ShopList_Sale.csv b/titles/cxb/rss2_data/Shop/ShopList_Sale.csv new file mode 100644 index 0000000..64438de --- /dev/null +++ b/titles/cxb/rss2_data/Shop/ShopList_Sale.csv @@ -0,0 +1,3 @@ +saleID.,ŠJŽn“ú,I—¹“ú,ShopID,Price, +0,1411696799,4096483201,0,7000, +1,1411783199,4096483201,1,7000, diff --git a/titles/cxb/rss2_data/Shop/ShopList_SkinBg.csv b/titles/cxb/rss2_data/Shop/ShopList_SkinBg.csv new file mode 100644 index 0000000..4c0bef2 --- /dev/null +++ b/titles/cxb/rss2_data/Shop/ShopList_SkinBg.csv @@ -0,0 +1,4 @@ +shopID.,®——pNo.,Ver.,oŒ»ƒtƒ‰ƒO,oŒ»ƒtƒ‰ƒOŽQÆID,ðŒ,oŒ»ŽžŠÔ,Á–ÅŽžŠÔ,ItemCode,‰¿Ši,•\Ž¦ƒ^ƒCƒv,Text,Type,Value(op),Value,‘ÎÛ‹È,Difficulty(op),Difficulty,Level(op),Level,Grade(Op),Grade,GaugeType(op),GaugeType,HS(op)i,HS,APP,DAP,F-V,F-H,FullCombo,Combo(op),Combo,ClearRate(op),ClearRate,ƒvƒŒƒC“ú,’nˆæ, +3000,1,10000,1,-1,-,1411697520,4096483201,skb0000,10,1,MASTERˆÈã‚Ì2‹ÈS+ˆÈãƒtƒ‹ƒRƒ“ƒ{ƒNƒŠƒA‚·‚éB,1,1,1,-,1,2,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +3001,2,10000,1,-1,-,1411697520,4096483201,skb0001,10,1,Next Frontier (Masterj‚ðƒNƒŠƒA,0,-1,-1,bleeze,1,2,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +3002,3,10000,1,-1,-,1412103600,4096483201,skb0002,10,2,MasterˆÈã‚ð1‹È‚ðS+ˆÈã‚ŃNƒŠƒA‚·‚éB,1,1,1,-,1,2,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,1, diff --git a/titles/cxb/rss2_data/Shop/ShopList_SkinEffect.csv b/titles/cxb/rss2_data/Shop/ShopList_SkinEffect.csv new file mode 100644 index 0000000..228155c --- /dev/null +++ b/titles/cxb/rss2_data/Shop/ShopList_SkinEffect.csv @@ -0,0 +1,11 @@ +shopID.,®——pNo.,Ver.,oŒ»ƒtƒ‰ƒO,oŒ»ƒtƒ‰ƒOŽQÆID,ðŒ,oŒ»ŽžŠÔ,Á–ÅŽžŠÔ,ItemCode,‰¿Ši,•\Ž¦ƒ^ƒCƒv,Text,Type,Value(op),Value,‘ÎÛ‹È,Difficulty(op),Difficulty,Level(op),Level,Grade(Op),Grade,GaugeType(op),GaugeType,HS(op)i,HS,APP,DAP,F-V,F-H,FullCombo,Combo(op),Combo,ClearRate(op),ClearRate,ƒvƒŒƒC“ú,’nˆæ, +5000,1,10000,1,-1,-,1411697520,4096483201,ske0000,10,1,MASTERˆÈã‚Ì2‹ÈS+ˆÈãƒtƒ‹ƒRƒ“ƒ{ƒNƒŠƒA‚·‚éB,1,1,1,-,1,2,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +5001,2,10000,1,-1,-,1411697520,4096483201,ske0001,10,1,Next Frontier (Masterj‚ðƒNƒŠƒA,0,-1,-1,megaro,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +5002,3,10000,1,-1,-,1412103600,4096483201,ske0002,10,2,MasterˆÈã‚ð1‹È‚ðS+ˆÈã‚ŃNƒŠƒA‚·‚éB,1,1,1,-,1,2,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,1, +5003,4,10000,1,-1,-,1412103600,4096483201,ske0003,10,0,MasterˆÈã‚ð1‹È‚ðS+ˆÈã‚ŃNƒŠƒA‚·‚éB,1,1,1,-,1,2,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +5004,5,10000,1,-1,-,1412103600,4096483201,ske0004,10,2,2‹ÈƒNƒŠƒA,1,1,2,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +5005,5,10000,1,-1,-,1412103600,4096483201,ske0005,10,2,3‹ÈƒNƒŠƒA,1,1,3,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +5006,5,10000,1,-1,-,1412103600,4096483201,ske0006,10,2,4‹ÈƒNƒŠƒA,1,1,4,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +5007,5,10000,1,-1,-,1412103600,4096483201,ske0007,10,2,5‹ÈƒNƒŠƒA,1,1,5,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +5008,5,10000,1,-1,-,1412103600,4096483201,ske0008,10,2,6‹ÈƒNƒŠƒA,1,1,6,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +5009,5,10000,1,-1,-,1412103600,4096483201,ske0009,10,2,7‹ÈƒNƒŠƒA,1,1,7,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, diff --git a/titles/cxb/rss2_data/Shop/ShopList_SkinNotes.csv b/titles/cxb/rss2_data/Shop/ShopList_SkinNotes.csv new file mode 100644 index 0000000..4fd4cf2 --- /dev/null +++ b/titles/cxb/rss2_data/Shop/ShopList_SkinNotes.csv @@ -0,0 +1,6 @@ +shopID.,®——pNo.,Ver.,oŒ»ƒtƒ‰ƒO,oŒ»ƒtƒ‰ƒOŽQÆID,ðŒ,oŒ»ŽžŠÔ,Á–ÅŽžŠÔ,ItemCode,‰¿Ši,•\Ž¦ƒ^ƒCƒv,Text,Type,Value(op),Value,‘ÎÛ‹È,Difficulty(op),Difficulty,Level(op),Level,Grade(Op),Grade,GaugeType(op),GaugeType,HS(op)i,HS,APP,DAP,F-V,F-H,FullCombo,Combo(op),Combo,ClearRate(op),ClearRate,ƒvƒŒƒC“ú,’nˆæ, +4000,1,10000,1,-1,-,1411697520,4096483201,skt0000,10,1,MASTERˆÈã‚Ì2‹ÈS+ˆÈãƒtƒ‹ƒRƒ“ƒ{ƒNƒŠƒA‚·‚éB,1,1,1,-,1,2,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +4001,2,10000,1,-1,-,1411697520,4096483201,skt0001,10,1,Next Frontier (Masterj‚ðƒNƒŠƒA,0,-1,-1,megaro,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +4002,3,10000,1,-1,-,1412103600,4096483201,skt0002,10,2,MasterˆÈã‚ð1‹È‚ðS+ˆÈã‚ŃNƒŠƒA‚·‚éB,1,1,1,-,1,2,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,1, +4003,4,10000,1,-1,-,1412103600,4096483201,skt0003,10,0,MasterˆÈã‚ð1‹È‚ðS+ˆÈã‚ŃNƒŠƒA‚·‚éB,1,1,1,-,1,2,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, +4004,5,10000,1,-1,-,1412103600,4096483201,skt0004,10,2,aaaaaaaaaaaaaaaaa,1,1,20,-,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,,, diff --git a/titles/cxb/rss2_data/Shop/ShopList_Title.csv b/titles/cxb/rss2_data/Shop/ShopList_Title.csv new file mode 100644 index 0000000..f42f002 --- /dev/null +++ b/titles/cxb/rss2_data/Shop/ShopList_Title.csv @@ -0,0 +1,265 @@ +2000,1001,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2001,1002,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2002,1003,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2003,1004,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2004,1005,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2005,1006,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2006,1007,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2007,1008,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2008,1009,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2009,1010,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2010,1011,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2011,1012,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2012,1013,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2013,1014,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2014,1015,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2015,1016,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2016,1017,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2017,1018,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2018,1019,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2019,1020,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2020,1021,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2021,1022,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2022,1023,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2023,1024,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2024,1025,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2025,1026,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2026,1027,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2027,1028,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2028,1029,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2029,1030,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2030,1031,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2031,1032,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2032,1033,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2033,1034,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2034,1035,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2035,1036,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2036,1037,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2037,1038,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2038,1039,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2039,1040,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2040,1041,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2041,1042,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2042,1043,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2043,1044,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2044,1045,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2045,1046,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2046,1047,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2047,1048,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2048,1049,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2049,1050,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2050,1051,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2051,1052,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2052,1053,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2053,1054,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2054,1055,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2055,1056,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2056,1057,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2057,1058,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2058,1059,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2059,1060,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2060,1061,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2061,1062,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2062,1063,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2063,1064,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2064,1065,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2065,1066,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2066,1067,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2067,1068,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2068,1069,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2069,1070,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2070,1071,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2071,1072,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2072,1073,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2073,1074,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2074,1075,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2075,1076,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2076,1077,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2077,1078,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2078,1079,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2079,1080,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2080,1081,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2081,1082,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2082,1083,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2083,1084,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2084,1085,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2085,1086,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2086,1087,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2087,1088,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2088,1089,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2089,1090,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2090,1091,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2091,1092,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2092,1093,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2093,1094,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2094,1095,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2095,1096,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2096,1097,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2097,1098,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2098,1099,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2099,1100,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2100,1101,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2101,1102,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2102,1103,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2103,1104,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2104,1105,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2105,1106,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2106,1107,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2107,1108,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2108,1109,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2109,1110,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2110,1111,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2111,1112,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2112,1113,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2113,1114,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2114,1115,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2115,1116,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2116,1117,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2117,1118,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2118,1119,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2119,1120,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2120,1121,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2121,1122,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2122,1123,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2123,1124,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2124,1125,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2125,1126,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2126,1127,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2127,1128,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2128,1129,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2129,1130,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2130,1131,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2131,1132,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2132,1133,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2133,1134,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2134,1135,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2135,1136,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2136,1137,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2137,1138,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2138,1139,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2139,1140,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2140,1141,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2141,1142,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2142,1143,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2143,1144,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2144,1145,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2145,1146,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2146,1147,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2147,1148,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2148,1149,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2149,1150,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2150,1151,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2151,1152,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2152,1153,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2153,1154,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2154,1155,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2155,1156,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2156,1157,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2157,1158,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2158,1159,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2159,1160,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2160,1161,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2161,1162,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2162,1163,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2163,1164,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2164,1165,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2165,1166,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2166,1167,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2167,1168,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2168,1169,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2169,1170,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2170,1171,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2171,1172,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2172,1173,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2173,1174,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2174,1175,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2175,1176,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2176,1177,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2177,1178,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2178,1179,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2179,1180,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2180,1181,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2181,1182,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2182,1183,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2183,1184,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2184,1185,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2185,1186,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2186,1187,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2187,1188,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2188,1189,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2189,1190,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2190,1191,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2191,1192,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2192,1193,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2193,1194,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2194,1195,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2195,1196,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2196,1197,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2197,1198,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2198,1199,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2199,1200,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2200,1201,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2201,1202,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2202,1203,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2203,1204,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2204,1205,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2205,1206,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2206,1207,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2207,1208,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2208,1209,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2209,1210,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2210,1211,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2211,1212,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2212,1213,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2213,1214,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2214,1215,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2215,1216,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2216,1217,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2217,1218,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2218,1219,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2219,1220,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2220,1221,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2221,1222,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2222,1223,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2223,1224,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2224,1225,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2225,1226,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2226,1227,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2227,1228,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2228,1229,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2229,1230,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2230,1231,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2231,1232,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2232,1233,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2233,1234,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2234,1235,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2235,1236,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2236,1237,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2237,1238,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2238,1239,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2239,1240,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2240,1241,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2241,1242,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2242,1243,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2243,1244,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2244,1245,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2245,1246,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2246,1247,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2247,1248,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2248,1249,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2249,1250,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2250,1251,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2251,1252,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2252,1253,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2253,1254,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2254,1255,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2255,1256,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2256,1257,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2257,1258,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2258,1259,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2259,1260,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2260,1261,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2261,1262,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2262,1263,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2263,1264,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, +2264,1265,10000,1,-1,-,1412103600,4096483201,,10,2,,1,,1,, diff --git a/titles/cxb/rss2_data/Tips.csv b/titles/cxb/rss2_data/Tips.csv new file mode 100644 index 0000000..7890089 --- /dev/null +++ b/titles/cxb/rss2_data/Tips.csv @@ -0,0 +1,27 @@ +RANK POINT(RP)ã¨ã¯ã€crossbeats REV.ã®ä¸Šæ‰‹ã•ã‚’表ã™æ•°å€¤ã§ã™ã€‚ +クリアã—ãŸæ¥½æ›²ã®ãƒ¬ãƒ™ãƒ«ã«ã‚¯ãƒªã‚¢ãƒ¬ãƒ¼ãƒˆã‚’掛ã‘ãŸã‚‚ã®ãŒã€ãã®æ›²ã®RPã«ãªã‚Šã¾ã™ã€‚ +自分ã®æˆç¸¾ã®ä¸Šä½ï¼’ï¼è­œé¢ãŒåŠ ç®—ã®å¯¾è±¡ã¨ãªã‚Šã¾ã™ã€‚ +ノートスピードã®èª¬æ˜Ž +ゲージタイプã®èª¬æ˜Ž +Movie Brightnessã®èª¬æ˜Ž +APP, DAPã®èª¬æ˜Ž +Flip-H, Flip-Vã®èª¬æ˜Ž +クリアレートã®èª¬æ˜Ž +グレードã®èª¬æ˜Ž +判定ã®èª¬æ˜Ž +スコアã®èª¬æ˜Ž +Challengeモードã®èª¬æ˜Ž +Local Battleã®èª¬æ˜Ž +REVCHIPã®èª¬æ˜Ž +REV.SHOPã®èª¬æ˜Ž +フィルターã®èª¬æ˜Ž +カテゴリーã®èª¬æ˜Ž +オプションã®èª¬æ˜Ž +DIFFICULTYã®èª¬æ˜Ž +クリア判定ã®èª¬æ˜Ž +ロングノートã®åˆ¤å®šã«ã¤ã„ã¦ã®èª¬æ˜Ž +フリックã®åˆ¤å®šã«ã¤ã„ã¦ã®èª¬æ˜Ž +オススメフォルダã®èª¬æ˜Ž +フルコンボã®èª¬æ˜Ž +連動Webã®èª¬æ˜Ž +MusicEnergyã®èª¬æ˜Ž diff --git a/titles/cxb/schema/__init__.py b/titles/cxb/schema/__init__.py new file mode 100644 index 0000000..ce70412 --- /dev/null +++ b/titles/cxb/schema/__init__.py @@ -0,0 +1,6 @@ +from titles.cxb.schema.profile import CxbProfileData +from titles.cxb.schema.score import CxbScoreData +from titles.cxb.schema.item import CxbItemData +from titles.cxb.schema.static import CxbStaticData + +__all__ = [CxbProfileData, CxbScoreData, CxbItemData, CxbStaticData] diff --git a/titles/cxb/schema/item.py b/titles/cxb/schema/item.py new file mode 100644 index 0000000..80d8427 --- /dev/null +++ b/titles/cxb/schema/item.py @@ -0,0 +1,45 @@ +from typing import Optional, Dict, List +from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_, case +from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean +from sqlalchemy.schema import ForeignKey +from sqlalchemy.sql import func +from sqlalchemy.dialects.mysql import insert + +from core.data.schema import BaseData, metadata + +energy = Table( + "cxb_rev_energy", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade"), nullable=False), + Column("energy", Integer, nullable=False, server_default="0"), + UniqueConstraint("user", name="cxb_rev_energy_uk"), + mysql_charset='utf8mb4' +) + +class CxbItemData(BaseData): + def put_energy(self, user_id: int, rev_energy: int) -> Optional[int]: + sql = insert(energy).values( + user = user_id, + energy = rev_energy + ) + + conflict = sql.on_duplicate_key_update( + energy = rev_energy + ) + + result = self.execute(conflict) + if result is None: + self.logger.error(f"{__name__} failed to insert item! user: {user_id}, energy: {rev_energy}") + return None + + return result.lastrowid + + def get_energy(self, user_id: int) -> Optional[Dict]: + sql = energy.select( + and_(energy.c.user == user_id) + ) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() diff --git a/titles/cxb/schema/profile.py b/titles/cxb/schema/profile.py new file mode 100644 index 0000000..1f731b4 --- /dev/null +++ b/titles/cxb/schema/profile.py @@ -0,0 +1,72 @@ +from typing import Optional, Dict, List +from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_ +from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON +from sqlalchemy.schema import ForeignKey +from sqlalchemy.sql import func, select +from sqlalchemy.dialects.mysql import insert + +from core.data.schema import BaseData, metadata + +profile = Table( + "cxb_profile", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade"), nullable=False), + Column("version", Integer, nullable=False), + Column("index", Integer, nullable=False), + Column("data", JSON, nullable=False), + UniqueConstraint("user", "index", name="cxb_profile_uk"), + mysql_charset='utf8mb4' +) + +class CxbProfileData(BaseData): + def put_profile(self, user_id: int, version: int, index: int, data: JSON) -> Optional[int]: + sql = insert(profile).values( + user = user_id, + version = version, + index = index, + data = data + ) + + conflict = sql.on_duplicate_key_update( + index = index, + data = data + ) + + result = self.execute(conflict) + if result is None: + self.logger.error(f"{__name__} failed to update! user: {user_id}, index: {index}, data: {data}") + return None + + return result.lastrowid + + def get_profile(self, aime_id: int, version: int) -> Optional[List[Dict]]: + """ + Given a game version and either a profile or aime id, return the profile + """ + sql = profile.select(and_( + profile.c.version == version, + profile.c.user == aime_id + )) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def get_profile_index(self, index: int, aime_id: int = None, version: int = None) -> Optional[Dict]: + """ + Given a game version and either a profile or aime id, return the profile + """ + if aime_id is not None and version is not None and index is not None: + sql = profile.select(and_( + profile.c.version == version, + profile.c.user == aime_id, + profile.c.index == index + )) + else: + self.logger.error(f"get_profile: Bad arguments!! aime_id {aime_id} version {version}") + return None + + result = self.execute(sql) + if result is None: return None + return result.fetchone() diff --git a/titles/cxb/schema/score.py b/titles/cxb/schema/score.py new file mode 100644 index 0000000..014e535 --- /dev/null +++ b/titles/cxb/schema/score.py @@ -0,0 +1,166 @@ +from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_ +from sqlalchemy.types import Integer, String, TIMESTAMP, JSON, Boolean +from sqlalchemy.schema import ForeignKey +from sqlalchemy.sql import func +from sqlalchemy.dialects.mysql import insert +from typing import Optional, List, Dict, Any + +from core.data.schema import BaseData, metadata +from core.data import cached + +score = Table( + "cxb_score", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade"), nullable=False), + Column("game_version", Integer), + Column("song_mcode", String(7)), + Column("song_index", Integer), + Column("data", JSON), + UniqueConstraint("user", "song_mcode", "song_index", name="cxb_score_uk"), + mysql_charset='utf8mb4' +) + +playlog = Table( + "cxb_playlog", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade"), nullable=False), + Column("song_mcode", String(7)), + Column("chart_id", Integer), + Column("score", Integer), + Column("clear", Integer), + Column("flawless", Integer), + Column("super", Integer), + Column("cool", Integer), + Column("fast", Integer), + Column("fast2", Integer), + Column("slow", Integer), + Column("slow2", Integer), + Column("fail", Integer), + Column("combo", Integer), + Column("date_scored", TIMESTAMP, server_default=func.now()), + mysql_charset='utf8mb4' +) + +ranking = Table( + "cxb_ranking", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade"), nullable=False), + Column("rev_id", Integer), + Column("song_id", Integer), + Column("score", Integer), + Column("clear", Integer), + UniqueConstraint("user", "rev_id", name="cxb_ranking_uk"), + mysql_charset='utf8mb4' +) + +class CxbScoreData(BaseData): + def put_best_score(self, user_id: int, song_mcode: str, game_version: int, song_index: int, data: JSON) -> Optional[int]: + """ + Update the user's best score for a chart + """ + sql = insert(score).values( + user=user_id, + song_mcode=song_mcode, + game_version=game_version, + song_index=song_index, + data=data + ) + + conflict = sql.on_duplicate_key_update( + data = sql.inserted.data + ) + + result = self.execute(conflict) + if result is None: + self.logger.error(f"{__name__} failed to insert best score! profile: {user_id}, song: {song_mcode}, data: {data}") + return None + + return result.lastrowid + + def put_playlog(self, user_id: int, song_mcode: str, chart_id: int, score: int, clear: int, flawless: int, this_super: int, + cool: int, this_fast: int, this_fast2: int, this_slow: int, this_slow2: int, fail: int, combo: int) -> Optional[int]: + """ + Add an entry to the user's play log + """ + sql = playlog.insert().values( + user=user_id, + song_mcode=song_mcode, + chart_id=chart_id, + score=score, + clear=clear, + flawless=flawless, + super=this_super, + cool=cool, + fast=this_fast, + fast2=this_fast2, + slow=this_slow, + slow2=this_slow2, + fail=fail, + combo=combo + ) + + result = self.execute(sql) + if result is None: + self.logger.error(f"{__name__} failed to insert playlog! profile: {user_id}, song: {song_mcode}, chart: {chart_id}") + return None + + return result.lastrowid + + def put_ranking(self, user_id: int, rev_id: int, song_id: int, score: int, clear: int) -> Optional[int]: + """ + Add an entry to the user's ranking logs + """ + if song_id == 0: + sql = insert(ranking).values( + user=user_id, + rev_id=rev_id, + score=score, + clear=clear + ) + else: + sql = insert(ranking).values( + user=user_id, + rev_id=rev_id, + song_id=song_id, + score=score, + clear=clear + ) + + conflict = sql.on_duplicate_key_update( + score = score + ) + + result = self.execute(conflict) + if result is None: + self.logger.error(f"{__name__} failed to insert ranking log! profile: {user_id}, score: {score}, clear: {clear}") + return None + + return result.lastrowid + + def get_best_score(self, user_id: int, song_mcode: int) -> Optional[Dict]: + sql = score.select( + and_(score.c.user == user_id, score.c.song_mcode == song_mcode) + ) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() + + def get_best_scores(self, user_id: int) -> Optional[Dict]: + sql = score.select(score.c.user == user_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def get_best_rankings(self, user_id: int) -> Optional[List[Dict]]: + sql = ranking.select( + ranking.c.user == user_id + ) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() diff --git a/titles/cxb/schema/static.py b/titles/cxb/schema/static.py new file mode 100644 index 0000000..6b16ac4 --- /dev/null +++ b/titles/cxb/schema/static.py @@ -0,0 +1,74 @@ +from typing import Dict, List, Optional +from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_ +from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, Float +from sqlalchemy.engine.base import Connection +from sqlalchemy.engine import Row +from sqlalchemy.schema import ForeignKey +from sqlalchemy.sql import func, select +from sqlalchemy.dialects.mysql import insert + +from core.data.schema import BaseData, metadata + +music = Table( + "cxb_static_music", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("version", Integer, nullable=False), + Column("songId", String(255)), + Column("index", Integer), + Column("chartId", Integer), + Column("title", String(255)), + Column("artist", String(255)), + Column("category", String(255)), + Column("level", Float), + UniqueConstraint("version", "songId", "chartId", "index", name="cxb_static_music_uk"), + mysql_charset='utf8mb4' +) + +class CxbStaticData(BaseData): + def put_music(self, version: int, mcode: str, index: int, chart: int, title: str, artist: str, category: str, level: float ) -> Optional[int]: + sql = insert(music).values( + version = version, + songId = mcode, + index = index, + chartId = chart, + title = title, + artist = artist, + category = category, + level = level + ) + + conflict = sql.on_duplicate_key_update( + title = title, + artist = artist, + category = category, + level = level + ) + + result = self.execute(conflict) + if result is None: return None + return result.lastrowid + + def get_music(self, version: int, song_id: Optional[int] = None) -> Optional[List[Row]]: + if song_id is None: + sql = select(music).where(music.c.version == version) + else: + sql = select(music).where(and_( + music.c.version == version, + music.c.songId == song_id, + )) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def get_music_chart(self, version: int, song_id: int, chart_id: int) -> Optional[List[Row]]: + sql = select(music).where(and_( + music.c.version == version, + music.c.songId == song_id, + music.c.chartId == chart_id + )) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() diff --git a/titles/diva/__init__.py b/titles/diva/__init__.py new file mode 100644 index 0000000..acc7ce4 --- /dev/null +++ b/titles/diva/__init__.py @@ -0,0 +1,18 @@ +from titles.diva.index import DivaServlet +from titles.diva.const import DivaConstants +from titles.diva.database import DivaData +from titles.diva.read import DivaReader + +index = DivaServlet +database = DivaData +reader = DivaReader + +use_default_title = True +include_protocol = True +title_secure = False +game_codes = [DivaConstants.GAME_CODE] +trailing_slash = True +use_default_host = False +host = "" + +current_schema_version = 1 \ No newline at end of file diff --git a/titles/diva/base.py b/titles/diva/base.py new file mode 100644 index 0000000..aef5841 --- /dev/null +++ b/titles/diva/base.py @@ -0,0 +1,506 @@ +import datetime +from typing import Any, List, Dict +import logging +import json +import urllib + +from core.config import CoreConfig +from titles.diva.config import DivaConfig +from titles.diva.const import DivaConstants +from titles.diva.database import DivaData + +class DivaBase(): + def __init__(self, cfg: CoreConfig, game_cfg: DivaConfig) -> None: + self.core_cfg = cfg # Config file + self.game_config = game_cfg + self.data = DivaData(cfg) # Database + self.date_time_format = "%Y-%m-%d %H:%M:%S" + self.logger = logging.getLogger("diva") + self.game = DivaConstants.GAME_CODE + self.version = DivaConstants.VER_PROJECT_DIVA_ARCADE_FUTURE_TONE + + dt = datetime.datetime.now() + self.time_lut=urllib.parse.quote(dt.strftime("%Y-%m-%d %H:%M:%S:16.0")) + + def handle_test_request(self, data: Dict) -> Dict: + return "" + + def handle_game_init_request(self, data: Dict) -> Dict: + return ( f'' ) + + def handle_attend_request(self, data: Dict) -> Dict: + encoded = "&" + params = { + 'atnd_prm1': '0,1,1,0,0,0,1,0,100,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1', + 'atnd_prm2': '30,10,100,4,1,50,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1', + 'atnd_prm3': '100,0,1,1,1,1,1,1,1,1,2,3,4,1,1,1,3,4,5,1,1,1,4,5,6,1,1,1,5,6,7,4,4,4,9,10,14,5,10,10,25,20,50,30,90,5,10,10,25,20,50,30,90,5,10,10,25,20,50,30,90,5,10,10,25,20,50,30,90,5,10,10,25,20,50,30,90,10,30,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0', + 'atnd_lut': f'{self.time_lut}', + } + + encoded += urllib.parse.urlencode(params) + encoded = encoded.replace("%2C", ",") + + return encoded + + def handle_ping_request(self, data: Dict) -> Dict: + encoded = "&" + params = { + 'ping_b_msg': f'Welcome to {self.core_cfg.server.name} network!', + 'ping_m_msg': 'xxx', + 'atnd_lut': f'{self.time_lut}', + 'fi_lut': f'{self.time_lut}', + 'ci_lut': f'{self.time_lut}', + 'qi_lut': f'{self.time_lut}', + 'pvl_lut': '2021-05-22 12:08:16.0', + 'shp_ctlg_lut': '2020-06-10 19:44:16.0', + 'cstmz_itm_ctlg_lut': '2019-10-08 20:23:12.0', + 'ngwl_lut': '2019-10-08 20:23:12.0', + 'rnk_nv_lut': '2020-06-10 19:51:30.0', + 'rnk_ps_lut': f'{self.time_lut}', + 'bi_lut': '2020-09-18 10:00:00.0', + 'cpi_lut': '2020-10-25 09:25:10.0', + 'bdlol_lut': '2020-09-18 10:00:00.0', + 'p_std_hc_lut': '2019-08-01 04:00:36.0', + 'p_std_i_n_lut': '2019-08-01 04:00:36.0', + 'pdcl_lut': '2019-08-01 04:00:36.0', + 'pnml_lut': '2019-08-01 04:00:36.0', + 'cinml_lut': '2019-08-01 04:00:36.0', + 'rwl_lut': '2019-08-01 04:00:36.0', + 'req_inv_cmd_num': '-1,-1,-1,-1,-1,-1,-1,-1,-1,-1', + 'req_inv_cmd_prm1': '-1,-1,-1,-1,-1,-1,-1,-1,-1,-1', + 'req_inv_cmd_prm2': '-1,-1,-1,-1,-1,-1,-1,-1,-1,-1', + 'req_inv_cmd_prm3': '-1,-1,-1,-1,-1,-1,-1,-1,-1,-1', + 'req_inv_cmd_prm4': '-1,-1,-1,-1,-1,-1,-1,-1,-1,-1', + 'pow_save_flg': 0, + 'nblss_dnt_p': 100, + 'nblss_ltt_rl_vp': 1500, + 'nblss_ex_ltt_flg': 1, + 'nblss_dnt_st_tm': "2019-07-15 12:00:00.0", + 'nblss_dnt_ed_tm': "2019-09-17 12:00:00.0", + 'nblss_ltt_st_tm': "2019-09-18 12:00:00.0", + 'nblss_ltt_ed_tm': "2019-09-22 12:00:00.0", + } + + encoded += urllib.parse.urlencode(params) + encoded = encoded.replace("+", "%20") + encoded = encoded.replace("%2C", ",") + + return encoded + + def handle_pv_list_request(self, data: Dict) -> Dict: + pvlist = "" + with open(r"titles/diva/data/PvList0.dat", encoding="utf-8") as shop: + lines = shop.readlines() + for line in lines: + pvlist += f"{line}" + pvlist += "," + + with open(r"titles/diva/data/PvList1.dat", encoding="utf-8") as shop: + lines = shop.readlines() + for line in lines: + pvlist += f"{line}" + pvlist += "," + + with open(r"titles/diva/data/PvList2.dat", encoding="utf-8") as shop: + lines = shop.readlines() + for line in lines: + pvlist += f"{line}" + pvlist += "," + + with open(r"titles/diva/data/PvList3.dat", encoding="utf-8") as shop: + lines = shop.readlines() + for line in lines: + pvlist += f"{line}" + pvlist += "," + + with open(r"titles/diva/data/PvList4.dat", encoding="utf-8") as shop: + lines = shop.readlines() + for line in lines: + pvlist += f"{line}" + + response = "" + response += f"&pvl_lut={self.time_lut}" + response += f"&pv_lst={pvlist}" + + return ( response ) + + def handle_shop_catalog_request(self, data: Dict) -> Dict: + catalog = "" + + shopList = self.data.static.get_enabled_shop(self.version) + if not shopList: + with open(r"titles/diva/data/ShopCatalog.dat", encoding="utf-8") as shop: + lines = shop.readlines() + for line in lines: + line = urllib.parse.quote(line) + "," + catalog += f"{urllib.parse.quote(line)}" + catalog = catalog.replace("+", "%20") + + response = "" + response += f"&shp_ctlg_lut={self.time_lut}" + response += f"&shp_ctlg={catalog[:-3]}" + else: + for shop in shopList: + line = str(shop["shopId"]) + "," + str(shop['unknown_0']) + "," + shop['name'] + "," + str(shop['points']) + "," + shop['start_date'] + "," + shop['end_date'] + "," + str(shop["type"]) + line = urllib.parse.quote(line) + "," + catalog += f"{urllib.parse.quote(line)}" + + catalog = catalog.replace("+", "%20") + + response = "" + response += f"&shp_ctlg_lut={self.time_lut}" + response += f"&shp_ctlg={catalog[:-3]}" + + return ( response ) + + def handle_cstmz_itm_ctlg_request(self, data: Dict) -> Dict: + catalog = "" + + itemList = self.data.static.get_enabled_items(self.version) + if not itemList: + with open(r"titles/diva/data/ItemCatalog.dat", encoding="utf-8") as item: + lines = item.readlines() + for line in lines: + line = urllib.parse.quote(line) + "," + catalog += f"{urllib.parse.quote(line)}" + catalog = catalog.replace("+", "%20") + + response = "" + response += f"&cstmz_itm_ctlg_lut={self.time_lut}" + response += f"&cstmz_itm_ctlg={catalog[:-3]}" + else: + for item in itemList: + line = str(item["itemId"]) + "," + str(item['unknown_0']) + "," + item['name'] + "," + str(item['points']) + "," + item['start_date'] + "," + item['end_date'] + "," + str(item["type"]) + line = urllib.parse.quote(line) + "," + catalog += f"{urllib.parse.quote(line)}" + + catalog = catalog.replace("+", "%20") + + response = "" + response += f"&cstmz_itm_ctlg_lut={self.time_lut}" + response += f"&cstmz_itm_ctlg={catalog[:-3]}" + + return ( response ) + + def handle_festa_info_request(self, data: Dict) -> Dict: + encoded = "&" + params = { + 'fi_id': '1,-1', + 'fi_name': f'{self.core_cfg.server.name} Opening,xxx', + 'fi_kind': '0,0', + 'fi_difficulty': '-1,-1', + 'fi_pv_id_lst': 'ALL,ALL', + 'fi_attr': '7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', + 'fi_add_vp': '10,0', + 'fi_mul_vp': '1,1', + 'fi_st': '2022-06-17 17:00:00.0,2014-07-08 18:10:11.0', + 'fi_et': '2029-01-01 10:00:00.0,2014-07-08 18:10:11.0', + 'fi_lut': '{self.time_lut}', + } + + encoded += urllib.parse.urlencode(params) + encoded = encoded.replace("+", "%20") + encoded = encoded.replace("%2C", ",") + + return encoded + + def handle_contest_info_request(self, data: Dict) -> Dict: + response = "" + + response += f"&ci_lut={self.time_lut}" + response += "&ci_str=%2A%2A%2A,%2A%2A%2A,%2A%2A%2A,%2A%2A%2A,%2A%2A%2A,%2A%2A%2A,%2A%2A%2A,%2A%2A%2A" + + return ( response ) + + def handle_qst_inf_request(self, data: Dict) -> Dict: + quest = "" + + questList = self.data.static.get_enabled_quests(self.version) + if not questList: + with open(r"titles/diva/data/QuestInfo.dat", encoding="utf-8") as shop: + lines = shop.readlines() + for line in lines: + quest += f"{urllib.parse.quote(line)}," + + response = "" + response += f"&qi_lut={self.time_lut}" + response += f"&qhi_str={quest[:-1]}" + else: + for quests in questList: + line = str(quests["questId"]) + "," + str(quests['quest_order']) + "," + str(quests['kind']) + "," + str(quests['unknown_0']) + "," + quests['start_datetime'] + "," + quests['end_datetime'] + "," + quests["name"] + "," + str(quests["unknown_1"]) + "," + str(quests["unknown_2"]) + "," + str(quests["quest_enable"]) + quest += f"{urllib.parse.quote(line)}%0A," + + responseline = f"{quest[:-1]}," + for i in range(len(questList),59): + responseline += "%2A%2A%2A%0A," + + response = "" + response += f"&qi_lut={self.time_lut}" + response += f"&qhi_str={responseline}%2A%2A%2A" + + response += "&qrai_str=%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1,%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1,%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1" + + return ( response ) + + def handle_nv_ranking_request(self, data: Dict) -> Dict: + return ( f'' ) + + def handle_ps_ranking_request(self, data: Dict) -> Dict: + return ( f'' ) + + def handle_ng_word_request(self, data: Dict) -> Dict: + return ( f'' ) + + def handle_rmt_wp_list_request(self, data: Dict) -> Dict: + return ( f'' ) + + def handle_pv_def_chr_list_request(self, data: Dict) -> Dict: + return ( f'' ) + + def handle_pv_ng_mdl_list_request(self, data: Dict) -> Dict: + return ( f'' ) + + def handle_cstmz_itm_ng_mdl_lst_request(self, data: Dict) -> Dict: + return ( f'' ) + + def handle_banner_info_request(self, data: Dict) -> Dict: + return ( f'' ) + + def handle_banner_data_request(self, data: Dict) -> Dict: + return ( f'' ) + + def handle_cm_ply_info_request(self, data: Dict) -> Dict: + return ( f'' ) + + def handle_pstd_h_ctrl_request(self, data: Dict) -> Dict: + return ( f'' ) + + def handle_pstd_item_ng_lst_request(self, data: Dict) -> Dict: + return ( f'' ) + + def handle_pre_start_request(self, data: Dict) -> Dict: + profile = self.data.profile.get_profile(data["aime_id"], self.version) + profile_shop = self.data.item.get_shop(data["aime_id"], self.version) + if profile is None: + return ( f"&ps_result=-3") + else: + response = "" + response += "&ps_result=1" + response += f"&pd_id={data['aime_id']}" + response += "&nblss_ltt_stts=-1" + response += "&nblss_ltt_tckt=-1" + response += "&nblss_ltt_is_opn=-1" + response += f"&vcld_pts={profile['vcld_pts']}" + response += f"&player_name={profile['player_name']}" + response += f"&lv_efct_id={profile['lv_efct_id']}" + response += f"&lv_plt_id={profile['lv_plt_id']}" + response += f"&lv_str={profile['lv_str']}" + response += f"&lv_num={profile['lv_num']}" + response += f"&lv_pnt={profile['lv_pnt']}" + + #Store stuff to add to rework + response += f"&mdl_eqp_tm={self.time_lut}" + + if profile_shop: + response += f"&mdl_eqp_ary={profile_shop['mdl_eqp_ary']}" + + response += f"&c_itm_eqp_ary=-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999" + response += f"&ms_itm_flg_ary=1,1,1,1,1,1,1,1,1,1,1,1" + + return ( response ) + + def handle_registration_request(self, data: Dict) -> Dict: #DONE + self.data.profile.create_profile(self.version, data["aime_id"], data["player_name"]) + return ( f"&cd_adm_result=1&pd_id={data['aime_id']}") + + def handle_start_request(self, data: Dict) -> Dict: + profile = self.data.profile.get_profile(data["pd_id"], self.version) + profile_shop = self.data.item.get_shop(data["pd_id"], self.version) + if profile is None: return + + response = "" + response += f"&pd_id={data['pd_id']}" + response += "&start_result=1" + + response += "&accept_idx=100" + response += f"&hp_vol={profile['hp_vol']}" + response += f"&btn_se_vol={profile['btn_se_vol']}" + response += f"&btn_se_vol2={profile['btn_se_vol2']}" + response += f"&sldr_se_vol2={profile['sldr_se_vol2']}" + response += f"&sort_kind={profile['sort_kind']}" + response += f"&player_name={profile['player_name']}" + response += f"&lv_num={profile['lv_num']}" + response += f"&lv_pnt={profile['lv_pnt']}" + response += f"&lv_efct_id={profile['lv_efct_id']}" + response += f"&lv_plt_id={profile['lv_plt_id']}" + response += "&mdl_have=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + response += "&cstmz_itm_have=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + response += f"&use_pv_mdl_eqp={profile['use_pv_mdl_eqp']}" + response += f"&use_pv_btn_se_eqp={profile['use_pv_btn_se_eqp']}" + response += f"&use_pv_sld_se_eqp={profile['use_pv_sld_se_eqp']}" + response += f"&use_pv_chn_sld_se_eqp={profile['use_pv_chn_sld_se_eqp']}" + response += f"&use_pv_sldr_tch_se_eqp={profile['use_pv_sldr_tch_se_eqp']}" + response += f"&vcld_pts={profile['lv_efct_id']}" + response += f"&nxt_pv_id={profile['nxt_pv_id']}" + response += f"&nxt_dffclty={profile['nxt_dffclty']}" + response += f"&nxt_edtn={profile['nxt_edtn']}" + response += f"&dsp_clr_brdr={profile['dsp_clr_brdr']}" + response += f"&dsp_intrm_rnk={profile['dsp_intrm_rnk']}" + response += f"&dsp_clr_sts={profile['dsp_clr_sts']}" + response += f"&rgo_sts={profile['rgo_sts']}" + + #To be fully fixed + if "my_qst_id" not in profile: + response += f"&my_qst_id=-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1" + response += f"&my_qst_sts=0,0,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1" + else: + response += f"&my_qst_id={profile['my_qst_id']}" + response += f"&my_qst_sts={profile['my_qst_sts']}" + + response += f"&my_qst_prgrs=0,0,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1" + response += f"&my_qst_et=2022-06-19%2010%3A28%3A52.0,2022-06-19%2010%3A28%3A52.0,2022-06-19%2010%3A28%3A52.0,2100-01-01%2008%3A59%3A59.0,2100-01-01%2008%3A59%3A59.0,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx" + response += f"&clr_sts=0,0,0,0,0,0,0,0,56,52,35,6,6,3,1,0,0,0,0,0" + + #Store stuff to add to rework + response += f"&mdl_eqp_tm={self.time_lut}" + + if profile_shop: + response += f"&mdl_eqp_ary={profile_shop['mdl_eqp_ary']}" + + response += f"&c_itm_eqp_ary=-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999" + response += f"&ms_itm_flg_ary=1,1,1,1,1,1,1,1,1,1,1,1" + + return ( response ) + + def handle_pd_unlock_request(self, data: Dict) -> Dict: + return ( f'' ) + + def handle_spend_credit_request(self, data: Dict) -> Dict: + profile = self.data.profile.get_profile(data["pd_id"], self.version) + if profile is None: return + + response = "" + + response += "&cmpgn_rslt=-1,-1,x,-1,-1,x,x,-1,x,-1,-1,x,-1,-1,x,x,-1,x,-1,-1,x,-1,-1,x,x,-1,x,-1,-1,x,-1,-1,x,x,-1,x,-1,-1,x,-1,-1,x,x,-1,x,-1,-1,x,-1,-1,x,x,-1,x" + response += "&cmpgn_rslt_num=0" + response += f"&vcld_pts={profile['vcld_pts']}" + response += f"&lv_str={profile['lv_str']}" + response += f"&lv_efct_id={profile['lv_efct_id']}" + response += f"&lv_plt_id={profile['lv_plt_id']}" + + return ( response ) + + def handle_get_pv_pd_request(self, data: Dict) -> Dict: + song_id = data["pd_pv_id_lst"].split(",") + pv = "" + + for song in song_id: + if int(song) > 0: + pd_db_song = self.data.score.get_best_score(data["pd_id"], int(song), data["difficulty"]) + if pd_db_song is not None: + + pv += urllib.parse.quote(f"{song},0,{pd_db_song['clr_kind']},{pd_db_song['score']},{pd_db_song['atn_pnt']},{pd_db_song['sort_kind']},-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,1,1,1,1,1,1,1,1,1,1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,1337,1,1,1,0,0,0") + + else: + #self.logger.debug(f"No score saved for ID: {song}!") + pv += urllib.parse.quote(f"{song},0,-1,-1,-1,0,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,1,1,1,1,1,1,1,1,1,1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,1,1,1,0,0,0") + + #pv_no, edition, rslt, max_score, max_atn_pnt, challenge_kind, module_eqp[-999,-999,-999], customize_eqp[-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999], customize_flag[1,1,1,1,1,1,1,1,1,1,1,1], skin, btn_se, sld_se, chsld_se, sldtch_se, rvl_pd_id, rvl_score, rvl_attn_pnt, countrywide_ranking, rgo_hispeed, rgo_hidden, rgo_sudden, rgo_hispeed_cleared, rgo_hidden_cleared, rgo_sudden_cleared, chain_challenge_num, chain_challenge_max, chain_challenge_open, version + else: + pv += urllib.parse.quote(f"{song}***") + pv += "," + + response = "" + response += f"&pd_by_pv_id={pv[:-1]}" + response += "&pdddt_flg=0" + response += f"&pdddt_tm={self.time_lut}" + + return ( response ) + + def handle_stage_start_request(self, data: Dict) -> Dict: + return ( f'' ) + + def handle_stage_result_request(self, data: Dict) -> Dict: + + profile = self.data.profile.get_profile(data["pd_id"], self.version) + + pd_song_list = data["stg_ply_pv_id"].split(",") + pd_song_difficulty = data["stg_difficulty"].split(",") + pd_song_max_score = data["stg_score"].split(",") + pd_song_max_atn_pnt = data["stg_atn_pnt"].split(",") + pd_song_ranking = data["stg_clr_kind"].split(",") + pd_song_sort_kind = data["sort_kind"] + pd_song_cool_cnt = data["stg_cool_cnt"].split(",") + pd_song_fine_cnt = data["stg_fine_cnt"].split(",") + pd_song_safe_cnt = data["stg_safe_cnt"].split(",") + pd_song_sad_cnt = data["stg_sad_cnt"].split(",") + pd_song_worst_cnt = data["stg_wt_wg_cnt"].split(",") + pd_song_max_combo = data["stg_max_cmb"].split(",") + + for index, value in enumerate(pd_song_list): + if "-1" not in pd_song_list[index]: + profile_pd_db_song = self.data.score.get_best_score(data["pd_id"], pd_song_list[index], pd_song_difficulty[index]) + if profile_pd_db_song is None: + self.data.score.put_best_score(data["pd_id"], self.version, pd_song_list[index], pd_song_difficulty[index], pd_song_max_score[index], pd_song_max_atn_pnt[index], pd_song_ranking[index], pd_song_sort_kind, pd_song_cool_cnt[index], pd_song_fine_cnt[index], pd_song_safe_cnt[index], pd_song_sad_cnt[index], pd_song_worst_cnt[index], pd_song_max_combo[index]) + self.data.score.put_playlog(data["pd_id"], self.version, pd_song_list[index], pd_song_difficulty[index], pd_song_max_score[index], pd_song_max_atn_pnt[index], pd_song_ranking[index], pd_song_sort_kind, pd_song_cool_cnt[index], pd_song_fine_cnt[index], pd_song_safe_cnt[index], pd_song_sad_cnt[index], pd_song_worst_cnt[index], pd_song_max_combo[index]) + elif int(pd_song_max_score[index]) >= int(profile_pd_db_song["score"]): + self.data.score.put_best_score(data["pd_id"], self.version, pd_song_list[index], pd_song_difficulty[index], pd_song_max_score[index], pd_song_max_atn_pnt[index], pd_song_ranking[index], pd_song_sort_kind, pd_song_cool_cnt[index], pd_song_fine_cnt[index], pd_song_safe_cnt[index], pd_song_sad_cnt[index], pd_song_worst_cnt[index], pd_song_max_combo[index]) + self.data.score.put_playlog(data["pd_id"], self.version, pd_song_list[index], pd_song_difficulty[index], pd_song_max_score[index], pd_song_max_atn_pnt[index], pd_song_ranking[index], pd_song_sort_kind, pd_song_cool_cnt[index], pd_song_fine_cnt[index], pd_song_safe_cnt[index], pd_song_sad_cnt[index], pd_song_worst_cnt[index], pd_song_max_combo[index]) + elif int(pd_song_max_score[index]) != int(profile_pd_db_song["score"]): + self.data.score.put_playlog(data["pd_id"], self.version, pd_song_list[index], pd_song_difficulty[index], pd_song_max_score[index], pd_song_max_atn_pnt[index], pd_song_ranking[index], pd_song_sort_kind, pd_song_cool_cnt[index], pd_song_fine_cnt[index], pd_song_safe_cnt[index], pd_song_sad_cnt[index], pd_song_worst_cnt[index], pd_song_max_combo[index]) + + # Profile saving based on registration list + + old_level = int(profile['lv_num']) + new_level = (int(data["ttl_vp_add"]) + int(profile["lv_pnt"])) / 12 + + self.data.profile.update_profile(data["pd_id"], int(new_level), int(profile["lv_pnt"]) + int(data["ttl_vp_add"]), int(data["vcld_pts"]), int(data["hp_vol"]), int(data["btn_se_vol"]), int(data["btn_se_vol2"]), int(data["sldr_se_vol2"]), int(data["sort_kind"]), int(data["use_pv_mdl_eqp"]), profile["use_pv_btn_se_eqp"], profile["use_pv_sld_se_eqp"], profile["use_pv_chn_sld_se_eqp"], profile["use_pv_sldr_tch_se_eqp"], int(data["ply_pv_id"]), int(data["nxt_dffclty"]), int(data["nxt_edtn"]), profile["dsp_clr_brdr"], profile["dsp_intrm_rnk"], profile["dsp_clr_sts"], profile["rgo_sts"], profile["lv_efct_id"], profile["lv_plt_id"], data["my_qst_id"], data["my_qst_sts"]) + + response = "" + + response += "&chllng_kind=-1" + response += f"&lv_num_old={int(old_level)}" + response += f"&lv_pnt_old={int(profile['lv_pnt'])}" + response += f"&lv_num={int(profile['lv_num'])}" + response += f"&lv_str={profile['lv_str']}" + response += f"&lv_pnt={int(profile['lv_pnt']) + int(data['ttl_vp_add'])}" + response += f"&lv_efct_id={int(profile['lv_efct_id'])}" + response += f"&lv_plt_id={int(profile['lv_plt_id'])}" + response += f"&vcld_pts={int(data['vcld_pts'])}" + response += f"&prsnt_vcld_pts={int(profile['vcld_pts'])}" + response += "&cerwd_kind=-1" + response += "&cerwd_value=-1" + response += "&cerwd_str_0=***" + response += "&cerwd_str_1=***" + response += "&ttl_str_ary=xxx,xxx,xxx,xxx,xxx" + response += "&ttl_plt_id_ary=-1,-1,-1,-1,-1" + response += "&ttl_desc_ary=xxx,xxx,xxx,xxx,xxx" + response += "&skin_id_ary=xxx,xxx,xxx,xxx,xxx" + response += "&skin_name_ary=xxx,xxx,xxx,xxx,xxx" + response += "&skin_illust_ary=xxx,xxx,xxx,xxx,xxx" + response += "&skin_desc_ary=xxx,xxx,xxx,xxx,xxx" + if "my_qst_id" not in profile: + response += f"&my_qst_id=-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1" + else: + response += f"&my_qst_id={profile['my_qst_id']}" + response += "&my_qst_r_qid=-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1" + response += "&my_qst_r_knd=-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1" + response += "&my_qst_r_vl=-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1" + response += "&my_qst_r_nflg=-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1" + response += "&my_ccd_r_qid=-1,-1,-1,-1,-1" + response += "&my_ccd_r_hnd=-1,-1,-1,-1,-1" + response += "&my_ccd_r_vp=-1,-1,-1,-1,-1" + + return ( response ) + + def handle_end_request(self, data: Dict) -> Dict: + profile = self.data.profile.get_profile(data["pd_id"], self.version) + self.data.profile.update_profile(data["pd_id"], profile["lv_num"], profile["lv_pnt"], profile["vcld_pts"], profile["hp_vol"], profile["btn_se_vol"], profile["btn_se_vol2"], profile["sldr_se_vol2"], profile["sort_kind"], profile["use_pv_mdl_eqp"], profile["use_pv_btn_se_eqp"], profile["use_pv_sld_se_eqp"], profile["use_pv_chn_sld_se_eqp"], profile["use_pv_sldr_tch_se_eqp"], profile["nxt_pv_id"], profile["nxt_dffclty"], profile["nxt_edtn"], profile["dsp_clr_brdr"], profile["dsp_intrm_rnk"], profile["dsp_clr_sts"], profile["rgo_sts"], profile["lv_efct_id"], profile["lv_plt_id"], data["my_qst_id"], data["my_qst_sts"]) + return ( f'' ) + + def handle_shop_exit_request(self, data: Dict) -> Dict: + self.data.item.put_shop(data["pd_id"], self.version, data["mdl_eqp_cmn_ary"]) + + response = "" + response += "&shp_rslt=1" + return ( response ) diff --git a/titles/diva/config.py b/titles/diva/config.py new file mode 100644 index 0000000..aba99f2 --- /dev/null +++ b/titles/diva/config.py @@ -0,0 +1,17 @@ +from core.config import CoreConfig + +class DivaServerConfig(): + def __init__(self, parent_config: "DivaConfig") -> None: + self.__config = parent_config + + @property + def enable(self) -> bool: + return CoreConfig.get_config_field(self.__config, 'diva', 'server', 'enable', default=True) + + @property + def loglevel(self) -> int: + return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'diva', 'server', 'loglevel', default="info")) + +class DivaConfig(dict): + def __init__(self) -> None: + self.server = DivaServerConfig(self) \ No newline at end of file diff --git a/titles/diva/const.py b/titles/diva/const.py new file mode 100644 index 0000000..44bbe36 --- /dev/null +++ b/titles/diva/const.py @@ -0,0 +1,11 @@ +class DivaConstants(): + GAME_CODE = "SBZV" + + VER_PROJECT_DIVA_ARCADE = 0 + VER_PROJECT_DIVA_ARCADE_FUTURE_TONE = 1 + + VERSION_NAMES = ("Project Diva Arcade", "Project Diva Arcade Future Tone") + + @classmethod + def game_ver_to_string(cls, ver: int): + return cls.VERSION_NAMES[ver] \ No newline at end of file diff --git a/titles/diva/data/ItemCatalog.dat b/titles/diva/data/ItemCatalog.dat new file mode 100644 index 0000000..0459830 --- /dev/null +++ b/titles/diva/data/ItemCatalog.dat @@ -0,0 +1,204 @@ +0,0,ç¸ãªã—メガãƒï¼ˆéŠ€ï¼‰,50,2013-09-13,2029-01-01,0 +1,0,ç¸ãªã—メガãƒï¼ˆèµ¤ï¼‰,50,2013-09-13,2029-01-01,1 +2,0,ナイロールメガãƒï¼ˆéŠ€ï¼‰,100,2013-09-13,2029-01-01,2 +3,0,ナイロールメガãƒï¼ˆèŒ¶ï¼‰,100,2014-03-18,2029-01-01,3 +4,0,フルフレームメガãƒï¼ˆæ©™ï¼‰,100,2014-03-18,2029-01-01,4 +5,0,フルフレームメガãƒï¼ˆé»’),100,2013-09-13,2029-01-01,5 +6,0,アンダーリムメガãƒï¼ˆé’),50,2014-02-12,2029-01-01,8 +7,0,アンダーリムメガãƒ(ピンク),50,2014-05-15,2029-01-01,9 +8,0,ãã‚‹ãるメガãƒ,150,2014-09-26,2029-01-01,15 +9,0,三角メガãƒï¼ˆé»’),100,2013-12-25,2029-01-01,10 +10,0,三角メガãƒï¼ˆèµ¤ï¼‰,100,2014-03-18,2029-01-01,11 +11,0,サングラス,150,2015-01-09,2029-01-01,16 +12,0,ゴーグル,150,2014-12-02,2029-01-01,17 +13,0,電脳ãƒã‚¤ã‚¶ãƒ¼,150,2015-01-09,2029-01-01,18 +14,0,片眼é¡,150,2015-04-01,2029-01-01,14 +15,0,アイマスク,150,2014-10-23,2029-01-01,19 +16,0,キラ目マスク,200,2015-05-20,2029-01-01,20 +17,0,眼帯(黒),150,2013-09-13,2029-01-01,23 +18,0,眼帯(緑),150,2013-12-25,2029-01-01,24 +19,0,眼帯(黄),150,2014-02-12,2029-01-01,25 +20,0,眼帯(オレンジ),150,2014-05-15,2029-01-01,26 +21,0,眼帯(ピンク),150,2014-05-15,2029-01-01,27 +22,0,眼帯(é’),150,2013-09-13,2029-01-01,28 +23,0,眼帯(赤),150,2013-09-13,2029-01-01,29 +24,0,眼帯(白),150,2015-07-22,2029-01-01,22 +25,0,京劇仮é¢ï¼ˆé’),200,2016-01-08,2029-01-01,33 +26,0,京劇仮é¢ï¼ˆèµ¤ï¼‰,200,2016-01-08,2029-01-01,34 +27,0,ãƒã‚¿ãƒ•ãƒ©ã‚¤ãƒžã‚¹ã‚¯,200,2015-07-22,2029-01-01,30 +28,0,マスカレードマスク,200,2014-07-24,2029-01-01,31 +30,0,ガスマスク,200,2015-07-22,2029-01-01,39 +31,0,能é¢ï¼ˆèˆ¬è‹¥ï¼‰,200,2015-07-22,2029-01-01,40 +32,0,能é¢ï¼ˆå¥³ï¼‰,200,2015-05-20,2029-01-01,41 +33,0,能é¢ï¼ˆç¿ï¼‰,200,2015-05-20,2029-01-01,42 +34,0,白マスク,150,2014-07-24,2029-01-01,35 +35,0,白マスク(ペロ舌),200,2014-09-26,2029-01-01,36 +36,0,白マスク(ω),200,2014-12-02,2029-01-01,37 +37,0,白マスク(ε),200,2015-05-20,2029-01-01,38 +38,0,ãƒã‚³ã²ã’,50,2015-04-01,2029-01-01,43 +39,0,天使ã®è¼ª,200,2015-07-22,2029-01-01,10 +40,0,ã²ã‚ˆã“,200,2014-07-24,2029-01-01,11 +41,0,ナースキャップ,100,2013-09-13,2029-01-01,0 +42,0,メイドカãƒãƒ¥ãƒ¼ã‚·ãƒ£,100,2013-12-25,2029-01-01,1 +43,0,ãƒã‚³ãƒŸãƒŸï¼ˆé»’),200,2013-09-13,2029-01-01,2 +44,0,ãƒã‚³ãƒŸãƒŸï¼ˆç™½ï¼‰,200,2013-12-25,2029-01-01,3 +45,0,ãƒã‚³ãƒŸãƒŸï¼ˆãƒˆãƒ©ï¼‰,200,2014-03-18,2029-01-01,4 +46,0,ウサミミ(黒),200,2014-02-12,2029-01-01,5 +47,0,ウサミミ(白),200,2013-09-13,2029-01-01,6 +48,0,ウサミミ(ピンク),200,2014-09-26,2029-01-01,7 +49,0,一本角,150,2014-03-18,2029-01-01,8 +50,0,悪魔ã®è§’,200,2015-05-20,2029-01-01,9 +51,0,クセ毛 A(緑),150,2014-05-15,2029-01-01,12 +52,0,クセ毛 B(黄),150,2014-07-24,2029-01-01,22 +53,0,クセ毛 C(黄),150,2014-09-26,2029-01-01,31 +54,0,クセ毛 D(ピンク),150,2014-10-23,2029-01-01,41 +55,0,クセ毛 E(é’),150,2014-12-02,2029-01-01,51 +56,0,クセ毛 F(ã“ã’茶),150,2015-01-09,2029-01-01,61 +57,0,è¶ãƒã‚¯ã‚¿ã‚¤ï¼ˆé‡‘),100,2013-12-25,2029-01-01,0 +58,0,è¶ãƒã‚¯ã‚¿ã‚¤ï¼ˆé»’),100,2013-09-13,2029-01-01,1 +59,0,è¶ãƒã‚¯ã‚¿ã‚¤ï¼ˆèµ¤ï¼‰,100,2013-09-13,2029-01-01,2 +60,0,リボン(é’),150,2014-02-12,2029-01-01,3 +61,0,リボン(黄),150,2013-09-13,2029-01-01,4 +62,0,リボン(ピンク),150,2013-09-13,2029-01-01,5 +67,0,鈴(金),150,2014-10-23,2029-01-01,6 +68,0,鈴(銀),150,2014-10-23,2029-01-01,7 +69,0,å…‰ã®ç¿¼,200,2014-12-02,2029-01-01,1 +70,0,天使ã®ç¿¼,200,2015-07-22,2029-01-01,0 +71,0,è¶ã®ç¾½æ ¹,200,2015-04-01,2029-01-01,3 +72,0,悪魔ã®ç¿¼,200,2015-05-20,2029-01-01,2 +73,0,ã¬ã„ãã‚‹ã¿,200,2013-09-13,2029-01-01,8 +74,0,リュックサック,150,2013-09-13,2029-01-01,4 +75,0,ナップサック,100,2013-12-25,2029-01-01,5 +76,0,ランドセル(黒),150,2014-02-12,2029-01-01,6 +77,0,ランドセル(赤),150,2014-02-12,2029-01-01,7 +78,0,ロケット,150,2014-07-24,2029-01-01,9 +79,0,ãƒã‚³ã—ã£ã½ï¼ˆé»’),200,2013-09-13,2029-01-01,11 +80,0,ãƒã‚³ã—ã£ã½ï¼ˆç™½ï¼‰,200,2013-12-25,2029-01-01,12 +81,0,ãƒã‚³ã—ã£ã½ï¼ˆãƒˆãƒ©ï¼‰,200,2014-03-18,2029-01-01,13 +82,0,ウサã—ã£ã½ï¼ˆé»’),200,2014-02-12,2029-01-01,14 +83,0,ウサã—ã£ã½ï¼ˆç™½ï¼‰,200,2013-09-13,2029-01-01,15 +84,0,ウサã—ã£ã½ï¼ˆãƒ”ンク),200,2014-09-26,2029-01-01,16 +85,0,ç‹ã—ã£ã½,350,2014-05-15,2029-01-01,17 +86,0,悪魔ã®å°¾,200,2015-01-09,2029-01-01,18 +87,0,赤ã·ã‚ˆã¼ã†,400,2016-09-21,2029-01-01,66 +88,0,ç·‘ã·ã‚ˆã®ã‹ã¿ã©ã‚,400,2016-09-21,2029-01-01,67 +89,0,ゼンマイ,150,2015-07-22,2029-01-01,10 +97,0,ゴールドクラウン,350,2016-01-08,2029-01-01,70 +98,0,プラãƒãƒŠã‚¯ãƒ©ã‚¦ãƒ³,350,2016-01-08,2029-01-01,71 +99,0,シャープメガãƒï¼ˆè—),100,2014-09-26,2029-01-01,6 +100,0,シャープメガãƒï¼ˆç´«ï¼‰,100,2013-12-25,2029-01-01,7 +101,0,丸メガãƒï¼ˆéŠ€ï¼‰,150,2013-12-25,2029-01-01,12 +102,0,丸メガãƒï¼ˆã¹ã£ç”²ï¼‰,150,2014-03-18,2029-01-01,13 +103,0,ãŸã“ルカ,400,2016-05-18,2029-01-01,68 +104,0,シテヤンヨ,400,2016-03-17,2029-01-01,69 +105,0,ã¯ã¡ã‚…ã­ãƒŸã‚¯,400,2014-12-02,2029-01-01,19 +106,0,リンã®å¹¼è™«,400,2016-01-08,2029-01-01,20 +107,0,劇画マスク,200,2016-01-08,2029-01-01,21 +115,0,シャープサングラス,150,2014-07-24,2029-01-01,45 +116,0,水中メガãƒ,200,2014-12-02,2029-01-01,44 +117,0,パーティー眼é¡,100,2015-04-01,2029-01-01,48 +118,0,ピエロ鼻,50,2015-05-20,2029-01-01,46 +119,0,キツãƒé¢,200,2016-03-17,2029-01-01,47 +120,0,ミニシルクãƒãƒƒãƒˆ,350,2014-05-15,2029-01-01,74 +121,0,コック帽,50,2013-09-13,2029-01-01,73 +122,0,çƒå¸½å­,50,2015-04-01,2029-01-01,72 +123,0,ミクダヨー,1500,2015-04-01,2029-01-01,76 +124,0,記章,100,2014-10-23,2029-01-01,8 +131,0,ãƒã‚¹ã‚¿ãƒ¼ä»˜ãƒªãƒ¥ãƒƒã‚¯ã‚µãƒƒã‚¯,150,2014-03-18,2029-01-01,21 +132,0,リコーダー付ランドセル(é»’),150,2015-01-09,2029-01-01,22 +133,0,リコーダー付ランドセル(赤),150,2015-01-09,2029-01-01,23 +134,0,ミクダヨー(ミニ),500,2014-10-23,2029-01-01,75 +135,0,クセ毛 A(黄),150,2014-05-15,2029-01-01,13 +136,0,クセ毛 A(ピンク),150,2014-05-15,2029-01-01,14 +137,0,クセ毛 A(é’),150,2014-05-15,2029-01-01,15 +138,0,クセ毛 A(ã“ã’茶),150,2014-05-15,2029-01-01,16 +139,0,クセ毛 A(金),150,2014-05-15,2029-01-01,17 +140,0,クセ毛 A(銀),150,2014-05-15,2029-01-01,18 +141,0,クセ毛 A(茶),150,2014-05-15,2029-01-01,19 +142,0,クセ毛 A(赤),150,2014-05-15,2029-01-01,20 +143,0,クセ毛 B(緑),150,2014-07-24,2029-01-01,21 +144,0,クセ毛 B(ピンク),150,2014-07-24,2029-01-01,23 +145,0,クセ毛 B(é’),150,2014-07-24,2029-01-01,24 +146,0,クセ毛 B(ã“ã’茶),150,2014-07-24,2029-01-01,25 +147,0,クセ毛 B(金),150,2014-07-24,2029-01-01,26 +148,0,クセ毛 B(銀),150,2014-07-24,2029-01-01,27 +149,0,クセ毛 B(茶),150,2014-07-24,2029-01-01,28 +150,0,クセ毛 B(赤),150,2014-07-24,2029-01-01,29 +151,0,クセ毛 C(緑),150,2014-09-26,2029-01-01,30 +152,0,クセ毛 C(ピンク),150,2014-09-26,2029-01-01,32 +153,0,クセ毛 C(é’),150,2014-09-26,2029-01-01,33 +154,0,クセ毛 C(ã“ã’茶),150,2014-09-26,2029-01-01,34 +155,0,クセ毛 C(金),150,2014-09-26,2029-01-01,35 +156,0,クセ毛 C(銀),150,2014-09-26,2029-01-01,36 +157,0,クセ毛 C(茶),150,2014-09-26,2029-01-01,37 +158,0,クセ毛 C(赤),150,2014-09-26,2029-01-01,38 +159,0,クセ毛 D(緑),150,2014-10-23,2029-01-01,39 +160,0,クセ毛 D(黄),150,2014-10-23,2029-01-01,40 +161,0,クセ毛 D(é’),150,2014-10-23,2029-01-01,42 +162,0,クセ毛 D(ã“ã’茶),150,2014-10-23,2029-01-01,43 +163,0,クセ毛 D(金),150,2014-10-23,2029-01-01,44 +164,0,クセ毛 D(銀),150,2014-10-23,2029-01-01,45 +165,0,クセ毛 D(茶),150,2014-10-23,2029-01-01,46 +166,0,クセ毛 D(赤),150,2014-10-23,2029-01-01,47 +167,0,クセ毛 E(緑),150,2014-12-02,2029-01-01,48 +168,0,クセ毛 E(黄),150,2014-12-02,2029-01-01,49 +169,0,クセ毛 E(ピンク),150,2014-12-02,2029-01-01,50 +170,0,クセ毛 E(ã“ã’茶),150,2014-12-02,2029-01-01,52 +171,0,クセ毛 E(金),150,2014-12-02,2029-01-01,53 +172,0,クセ毛 E(銀),150,2014-12-02,2029-01-01,54 +173,0,クセ毛 E(茶),150,2014-12-02,2029-01-01,55 +174,0,クセ毛 E(赤),150,2014-12-02,2029-01-01,56 +175,0,クセ毛 F(緑),150,2015-01-09,2029-01-01,57 +176,0,クセ毛 F(黄),150,2015-01-09,2029-01-01,58 +177,0,クセ毛 F(ピンク),150,2015-01-09,2029-01-01,59 +178,0,クセ毛 F(é’),150,2015-01-09,2029-01-01,60 +179,0,クセ毛 F(金),150,2015-01-09,2029-01-01,62 +180,0,クセ毛 F(銀),150,2015-01-09,2029-01-01,63 +181,0,クセ毛 F(茶),150,2015-01-09,2029-01-01,64 +182,0,クセ毛 F(赤),150,2015-01-09,2029-01-01,65 +183,0,イヌミミ,200,2015-10-15,2029-01-01,77 +184,0,羊角,200,2016-09-21,2029-01-01,78 +185,0,悪魔ã®é ­ç¾½,200,2016-09-21,2029-01-01,79 +186,0,ã¨ã‚Šã®å·£,200,2016-03-17,2029-01-01,80 +187,0,ãŠã°ã‘ã®ä¸‰è§’ãšãã‚“,200,2016-07-20,2029-01-01,81 +188,0,パラボラアンテナ,200,2016-11-24,2029-01-01,82 +189,0,パーティ帽(赤),100,2016-12-15,2029-01-01,83 +190,0,パーティ帽(é’),100,2016-12-15,2029-01-01,84 +191,0,パーティ帽(黄),100,2016-12-15,2029-01-01,85 +192,0,ホホジロザメ,300,2016-07-20,2029-01-01,86 +193,0,パンプキンヘッド,300,2015-10-15,2029-01-01,87 +194,0,雪ã ã‚‹ã¾ãƒ˜ãƒƒãƒ‰ï¼ˆãƒŽãƒ¼ãƒžãƒ«ï¼‰,300,2016-12-15,2029-01-01,88 +195,0,雪ã ã‚‹ã¾ãƒ˜ãƒƒãƒ‰ï¼ˆç¬‘顔),300,2016-12-15,2029-01-01,89 +196,0,ãƒãƒ¼ãƒˆï¼ˆé ­ï¼‰,200,2017-01-18,2029-01-01,90 +197,0,ã·ã‚“ã·ã‚“,200,2016-11-24,2029-01-01,91 +198,0,汗(å³ï¼‰,100,2015-10-15,2029-01-01,92 +199,0,汗(左),100,2015-10-15,2029-01-01,93 +200,0,ï¼ï¼Ÿ,200,2016-11-24,2029-01-01,94 +201,0,ã§ã‹ãƒ¡ã‚¬ãƒ,150,2016-07-20,2029-01-01,49 +204,0,ã¤ã‘ã²ã’,150,2016-12-15,2029-01-01,52 +205,0,ãã¡ã°ã—,100,2016-03-17,2029-01-01,53 +208,0,目隠ã—ç·š,200,2016-05-18,2029-01-01,56 +210,0,ã¤ã‘ãˆã‚Š,150,2015-04-01,2029-01-01,15 +211,0,ãƒãƒ¼ãƒ¢ãƒ‹ã‚«,150,2016-03-17,2029-01-01,16 +212,0,金メダル,200,2016-05-18,2029-01-01,17 +213,0,銀メダル,150,2016-05-18,2029-01-01,18 +214,0,銅メダル,100,2016-05-18,2029-01-01,19 +215,0,å‰ã‹ã‘(緑),100,2016-11-24,2029-01-01,20 +216,0,å‰ã‹ã‘(黄),100,2016-11-24,2029-01-01,21 +217,0,å‰ã‹ã‘(橙),100,2016-11-24,2029-01-01,22 +218,0,å‰ã‹ã‘(ピンク),100,2016-11-24,2029-01-01,23 +219,0,å‰ã‹ã‘(é’),100,2016-11-24,2029-01-01,24 +220,0,å‰ã‹ã‘(赤),100,2016-11-24,2029-01-01,25 +221,0,åˆå¿ƒè€…マーク,150,2016-07-20,2029-01-01,26 +222,0,タイマー(緑),50,2015-10-15,2029-01-01,27 +223,0,タイマー(赤),50,2015-10-15,2029-01-01,28 +224,0,ãƒãƒ¼ãƒˆï¼ˆèƒ¸ï¼‰,200,2017-01-18,2029-01-01,29 +225,0,失æ‹ãƒãƒ¼ãƒˆ,200,2017-01-18,2029-01-01,30 +226,0,機械ã®ç¿¼,200,2016-03-17,2029-01-01,24 +227,0,スクールãƒãƒƒã‚°,150,2017-01-18,2029-01-01,25 +228,0,押ã—ボタン,200,2016-05-18,2029-01-01,26 +229,0,イヌã—ã£ã½,200,2015-10-15,2029-01-01,27 +230,0,æç«œã—ã£ã½,200,2016-07-20,2029-01-01,28 +231,0,猫åˆã—ã£ã½,300,2016-09-21,2029-01-01,29 +232,0,ä¹å°¾ã—ã£ã½,300,2016-12-15,2029-01-01,30 +233,0,ã¶ã‚‰ã•ãŒã‚Šãƒã‚³,300,2017-01-18,2029-01-01,31 \ No newline at end of file diff --git a/titles/diva/data/PvList0.dat b/titles/diva/data/PvList0.dat new file mode 100644 index 0000000..7596877 --- /dev/null +++ b/titles/diva/data/PvList0.dat @@ -0,0 +1 @@ +1%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C2%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C3%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-08-07%252C2029-01-01%2C4%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C5%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C6%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-08-07%252C2029-01-01%2C7%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-12-03%a252C2029-01-01%2C8%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-09-05%252C2029-01-01%2C9%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C10%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-01-08%252C2029-01-01%2C11%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-05-23%252C2029-01-01%2C16%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C18%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C19%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C20%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C22%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C23%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C24%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-10-03%252C2029-01-01%2C25%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C27%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C28%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-08-07%252C2029-01-01%2C30%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-10-22%252C2029-01-01%2C37%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-06-06%252C2029-01-01%2C38%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-05-13%252C2029-01-01%2C39%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C40%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2011-10-18%252C2029-01-01%2C41%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C42%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-02-03%252C2029-01-01%2C43%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C44%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C45%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C46%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C47%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2011-11-30%252C2029-01-01%2C48%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C49%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-06-17%252C2029-01-01%2C50%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C51%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-18%252C2029-01-01%2C52%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C53%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-09-24%252C2029-01-01%2C54%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C55%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C56%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C57%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-12-03%252C2029-01-01%2C58%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C59%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C60%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-04-08%252C2029-01-01%2C61%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-02-03%252C2029-01-01%2C62%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-07-16%252C2029-01-01%2C63%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C64%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-05-13%252C2029-01-01%2C65%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-03-12%252C2029-01-01%2C66%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-11-13%252C2029-01-01%2C68%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C79%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-10-26%252C2029-01-01%2C81%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C83%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-10-26%252C2029-01-01%2C86%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-10-26%252C2029-01-01%2C88%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C92%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-10-26%252C2029-01-01%2C93%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-01-31%252C2029-01-01%2C94%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-09-24%252C2029-01-01%2C95%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-10-26%252C2029-01-01%2C96%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-18%252C2029-01-01%2C97%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-10-26%252C2029-01-01%2C102%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C204%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C206%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C208%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-09-05%252C2029-01-01%2C213%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2011-11-30%252C2029-01-01%2C216%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-08-31%252C2029-01-01%2C219%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-06-10%252C2029-01-01%2C220%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-08-31%252C2029-01-01%2C221%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-10-14%252C2029-01-01%2C225%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-10-22%252C2029-01-01%2C232%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-06-17%252C2029-01-01%2C234%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C235%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-12-20%252C2029-01-01%2C241%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C242%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-08-07%252C2029-01-01%2C243%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-07-16%252C2029-01-01%2C244%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C247%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C248%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-08-07%252C2029-01-01%2C249%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C250%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-05-13%252C2029-01-01%2C251%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-09-24%252C2029-01-01%2C253%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C254%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-12-21%252C2029-01-01%2C255%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-06-17%252C2029-01-01%2C257%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C259%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C260%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C261%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-06-17%252C2029-01-01%2C262%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-10-14%252C2029-01-01%2C263%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-12-21%252C2029-01-01%2C265%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-10-14%252C2029-01-01%2C266%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2017-12-13%252C2029-01-01%2C267%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2017-12-13%252C2029-01-01%2C402%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C405%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-01-08%252C2029-01-01%2C409%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C410%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-01-31%252C2029-01-01%2C415%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-06-10%252C2029-01-01%2C416%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C418%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C419%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C424%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-03-12%252C2029-01-01%2C427%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2011-12-15%252C2029-01-01%2C429%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-08-07%252C2029-01-01%2C430%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-01-31%252C2029-01-01%2C432%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-05-27%252C2029-01-01%2C433%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C434%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-07-15%252C2029-01-01%2C436%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-09-27%252C2029-01-01%2C439%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-12-04%252C2029-01-01%2C440%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-12-04%252C2029-01-01%2C442%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C443%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-05-23%252C2029-01-01%2C600%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C601%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-02-10%252C2029-01-01%2C602%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C604%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C605%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-04-08%252C2029-01-01%2C608%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-05-27%252C2029-01-01%2C609%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-05-13%252C2029-01-01%2C610%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-05-27%252C2029-01-01%2C611%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C612%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-08-07%252C2029-01-01%2C613%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C615%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-12-20%252C2029-01-01%2C616%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C618%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-07-15%252C2029-01-01%2C620%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C622%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-12-20%252C2029-01-01%2C623%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-05-13%252C2029-01-01%2C624%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-07-15%252C2029-01-01%2C626%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C627%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-03-12%252C2029-01-01%2C628%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C630%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-09-18%252C2029-01-01%2C631%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C637%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-03-12%252C2029-01-01%2C638%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C640%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-12-04%252C2029-01-01%2C641%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-12-04%252C2029-01-01%2C642%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-09-18%252C2029-01-01%2C710%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C722%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-07-16%252C2029-01-01%2C723%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-07-16%252C2029-01-01%2C724%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C725%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-01-08%252C2029-01-01%2C726%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-10-22%252C2029-01-01%2C727%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-05-11%252C2029-01-01%2C728%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-12-03%252C2029-01-01%2C729%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-06-16%252C2029-01-01%2C730%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C731%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-12-03%252C2029-01-01%2C732%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C733%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-01-08%252C2029-01-01%2C734%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-10-22%252C2029-01-01%2C736%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C737%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-12-03%252C2029-01-01%2C739%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-08-07%252C2029-01-01%2C740%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C832%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C \ No newline at end of file diff --git a/titles/diva/data/PvList1.dat b/titles/diva/data/PvList1.dat new file mode 100644 index 0000000..89285de --- /dev/null +++ b/titles/diva/data/PvList1.dat @@ -0,0 +1 @@ +1%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C2%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C3%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-08-07%252C2029-01-01%2C5%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C8%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-09-05%252C2029-01-01%2C11%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-05-23%252C2029-01-01%2C12%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C13%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-08-07%252C2029-01-01%2C14%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-10-03%252C2029-01-01%2C15%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-10-03%252C2029-01-01%2C17%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-07-16%252C2029-01-01%2C20%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C21%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C22%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C23%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C24%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-10-03%252C2029-01-01%2C25%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C27%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C28%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-08-07%252C2029-01-01%2C29%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-10-14%252C2029-01-01%2C30%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-10-22%252C2029-01-01%2C31%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C32%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-11-13%252C2029-01-01%2C37%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-06-06%252C2029-01-01%2C38%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-05-13%252C2029-01-01%2C39%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C40%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2011-10-18%252C2029-01-01%2C41%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C42%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-02-03%252C2029-01-01%2C43%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C44%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C45%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C46%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C47%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2011-11-30%252C2029-01-01%2C48%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C49%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-06-17%252C2029-01-01%2C50%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C51%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-18%252C2029-01-01%2C52%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C53%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-09-24%252C2029-01-01%2C54%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C55%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C56%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C57%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-12-03%252C2029-01-01%2C58%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C59%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C60%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-04-08%252C2029-01-01%2C61%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-02-03%252C2029-01-01%2C62%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-07-16%252C2029-01-01%2C63%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C64%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-05-13%252C2029-01-01%2C65%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-03-12%252C2029-01-01%2C66%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-11-13%252C2029-01-01%2C67%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C68%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C79%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-10-26%252C2029-01-01%2C81%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C82%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C83%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-10-26%252C2029-01-01%2C84%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-10-14%252C2029-01-01%2C85%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-12-03%252C2029-01-01%2C86%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-10-26%252C2029-01-01%2C87%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-04-08%252C2029-01-01%2C88%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C89%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-10-14%252C2029-01-01%2C90%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-07-16%252C2029-01-01%2C91%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-10-26%252C2029-01-01%2C92%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-10-26%252C2029-01-01%2C93%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-01-31%252C2029-01-01%2C94%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-09-24%252C2029-01-01%2C95%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-10-26%252C2029-01-01%2C96%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-18%252C2029-01-01%2C97%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-10-26%252C2029-01-01%2C101%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-09-05%252C2029-01-01%2C102%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C103%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-08-31%252C2029-01-01%2C104%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-08-31%252C2029-01-01%2C201%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C202%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C203%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-01-31%252C2029-01-01%2C204%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C205%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C206%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C207%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C208%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-09-05%252C2029-01-01%2C209%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-05-13%252C2029-01-01%2C210%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C211%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C212%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-10-22%252C2029-01-01%2C213%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2011-11-30%252C2029-01-01%2C214%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-08-31%252C2029-01-01%2C215%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-05-28%252C2029-01-01%2C216%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-08-31%252C2029-01-01%2C218%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-08-31%252C2029-01-01%2C219%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-06-10%252C2029-01-01%2C220%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-08-31%252C2029-01-01%2C221%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-10-14%252C2029-01-01%2C222%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-08-31%252C2029-01-01%2C223%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-12-20%252C2029-01-01%2C224%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-01-08%252C2029-01-01%2C225%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-10-22%252C2029-01-01%2C226%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-11-13%252C2029-01-01%2C227%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-08-31%252C2029-01-01%2C228%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-08-31%252C2029-01-01%2C231%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-08-07%252C2029-01-01%2C232%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-06-17%252C2029-01-01%2C233%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C234%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C235%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-12-20%252C2029-01-01%2C236%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-12-20%252C2029-01-01%2C238%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-02-10%252C2029-01-01%2C239%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-12-20%252C2029-01-01%2C240%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-12-20%252C2029-01-01%2C241%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C242%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-08-07%252C2029-01-01%2C243%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-07-16%252C2029-01-01%2C244%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C246%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-04-08%252C2029-01-01%2C247%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C248%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-08-07%252C2029-01-01%2C249%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C250%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-05-13%252C2029-01-01%2C251%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-09-24%252C2029-01-01%2C253%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C254%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-12-21%252C2029-01-01%2C255%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-06-17%252C2029-01-01%2C257%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C259%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C260%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C261%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-06-17%252C2029-01-01%2C262%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-10-14%252C2029-01-01%2C263%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-12-21%252C2029-01-01%2C265%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-10-14%252C2029-01-01%2C266%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2017-12-13%252C2029-01-01%2C267%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2017-12-13%252C2029-01-01%2C401%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-09-05%252C2029-01-01%2C402%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C403%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-10-03%252C2029-01-01%2C404%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-11-13%252C2029-01-01%2C405%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-01-08%252C2029-01-01%2C407%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-10-22%252C2029-01-01%2C408%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C409%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C410%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-01-31%252C2029-01-01%2C411%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C412%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C413%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-06-17%252C2029-01-01%2C414%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C415%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-06-10%252C2029-01-01%2C416%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C417%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C418%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C419%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C420%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C421%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-06-10%252C2029-01-01%2C422%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-04-08%252C2029-01-01%2C423%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C424%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-03-12%252C2029-01-01%2C425%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2011-12-15%252C2029-01-01%2C426%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2011-12-15%252C2029-01-01%2C427%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2011-12-15%252C2029-01-01%2C428%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C429%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-08-07%252C2029-01-01%2C430%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-01-31%252C2029-01-01%2C431%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-05-28%252C2029-01-01%2C432%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-05-27%252C2029-01-01%2C433%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C434%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-07-15%252C2029-01-01%2C435%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-10-14%252C2029-01-01%2C436%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-09-27%252C2029-01-01%2C437%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-09-27%252C2029-01-01%2C438%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C439%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-12-04%252C2029-01-01%2C440%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-12-04%252C2029-01-01%2C441%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C442%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C443%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-05-23%252C2029-01-01%2C600%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C601%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-02-10%252C2029-01-01%2C602%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C603%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C604%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C605%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-04-08%252C2029-01-01%2C607%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C608%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-05-27%252C2029-01-01%2C609%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-05-13%252C2029-01-01%2C610%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-05-27%252C2029-01-01%2C611%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C612%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-08-07%252C2029-01-01%2C613%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C614%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-05-27%252C2029-01-01%2C615%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-12-20%252C2029-01-01%2C616%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C617%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C618%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-07-15%252C2029-01-01%2C619%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C620%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C621%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C622%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-12-20%252C2029-01-01%2C623%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-05-13%252C2029-01-01%2C624%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-07-15%252C2029-01-01%2C625%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-08-07%252C2029-01-01%2C626%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C627%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-03-12%252C2029-01-01%2C628%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C629%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-02-10%252C2029-01-01%2C630%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-09-18%252C2029-01-01%2C631%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C637%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-03-12%252C2029-01-01%2C638%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C639%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-05-27%252C2029-01-01%2C640%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-12-04%252C2029-01-01%2C641%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-12-04%252C2029-01-01%2C642%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-09-18%252C2029-01-01%2C710%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C722%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-07-16%252C2029-01-01%2C723%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-07-16%252C2029-01-01%2C724%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C725%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-01-08%252C2029-01-01%2C726%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-10-22%252C2029-01-01%2C727%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-05-11%252C2029-01-01%2C728%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-12-03%252C2029-01-01%2C729%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-06-16%252C2029-01-01%2C730%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C731%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-12-03%252C2029-01-01%2C732%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C733%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-01-08%252C2029-01-01%2C734%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-10-22%252C2029-01-01%2C736%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C737%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-12-03%252C2029-01-01%2C738%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-01-08%252C2029-01-01%2C739%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-08-07%252C2029-01-01%2C740%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C832%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01 \ No newline at end of file diff --git a/titles/diva/data/PvList2.dat b/titles/diva/data/PvList2.dat new file mode 100644 index 0000000..fff1552 --- /dev/null +++ b/titles/diva/data/PvList2.dat @@ -0,0 +1 @@ +1%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C2%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C3%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-08-07%252C2029-01-01%2C4%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C5%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C6%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-08-07%252C2029-01-01%2C7%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-12-03%252C2029-01-01%2C8%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-09-05%252C2029-01-01%2C9%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C10%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-01-08%252C2029-01-01%2C11%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-05-23%252C2029-01-01%2C12%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C13%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-08-07%252C2029-01-01%2C14%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-10-03%252C2029-01-01%2C15%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-10-03%252C2029-01-01%2C16%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C17%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-07-16%252C2029-01-01%2C18%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C19%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C20%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C21%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C22%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C23%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C24%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-10-03%252C2029-01-01%2C25%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C27%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C28%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-08-07%252C2029-01-01%2C29%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-10-14%252C2029-01-01%2C30%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-10-22%252C2029-01-01%2C31%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C32%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-11-13%252C2029-01-01%2C37%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-06-06%252C2029-01-01%2C38%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-05-13%252C2029-01-01%2C39%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C40%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2011-10-18%252C2029-01-01%2C41%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C42%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-02-03%252C2029-01-01%2C43%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C44%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C45%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C46%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C47%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2011-11-30%252C2029-01-01%2C48%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C49%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-06-17%252C2029-01-01%2C50%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C51%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-18%252C2029-01-01%2C52%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C53%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-09-24%252C2029-01-01%2C54%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C55%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C56%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C57%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-12-03%252C2029-01-01%2C58%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C59%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C60%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-04-08%252C2029-01-01%2C61%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-02-03%252C2029-01-01%2C62%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-07-16%252C2029-01-01%2C63%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C64%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-05-13%252C2029-01-01%2C65%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-03-12%252C2029-01-01%2C66%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-11-13%252C2029-01-01%2C67%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C68%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C79%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-10-26%252C2029-01-01%2C81%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C82%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C83%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-10-26%252C2029-01-01%2C84%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-10-14%252C2029-01-01%2C85%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-12-03%252C2029-01-01%2C86%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-10-26%252C2029-01-01%2C87%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-04-08%252C2029-01-01%2C88%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C89%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-10-14%252C2029-01-01%2C90%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-07-16%252C2029-01-01%2C91%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-10-26%252C2029-01-01%2C92%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-10-26%252C2029-01-01%2C93%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-01-31%252C2029-01-01%2C94%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-09-24%252C2029-01-01%2C95%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-10-26%252C2029-01-01%2C96%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-18%252C2029-01-01%2C97%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-10-26%252C2029-01-01%2C101%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-09-05%252C2029-01-01%2C102%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C103%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-08-31%252C2029-01-01%2C104%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-08-31%252C2029-01-01%2C201%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C202%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C203%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-01-31%252C2029-01-01%2C204%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C205%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C206%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C207%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C208%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-09-05%252C2029-01-01%2C209%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-05-13%252C2029-01-01%2C210%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C211%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C212%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-10-22%252C2029-01-01%2C213%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2011-11-30%252C2029-01-01%2C214%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-08-31%252C2029-01-01%2C215%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-05-28%252C2029-01-01%2C216%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-08-31%252C2029-01-01%2C218%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-08-31%252C2029-01-01%2C219%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-06-10%252C2029-01-01%2C220%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-08-31%252C2029-01-01%2C221%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-10-14%252C2029-01-01%2C222%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-08-31%252C2029-01-01%2C223%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-12-20%252C2029-01-01%2C224%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-01-08%252C2029-01-01%2C225%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-10-22%252C2029-01-01%2C226%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-11-13%252C2029-01-01%2C227%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-08-31%252C2029-01-01%2C228%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-08-31%252C2029-01-01%2C231%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-08-07%252C2029-01-01%2C232%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-06-17%252C2029-01-01%2C233%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C234%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C235%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-12-20%252C2029-01-01%2C236%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-12-20%252C2029-01-01%2C238%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-02-10%252C2029-01-01%2C239%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-12-20%252C2029-01-01%2C240%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-12-20%252C2029-01-01%2C241%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C242%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-08-07%252C2029-01-01%2C243%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-07-16%252C2029-01-01%2C244%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C246%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-04-08%252C2029-01-01%2C247%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C248%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-08-07%252C2029-01-01%2C249%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C250%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-05-13%252C2029-01-01%2C251%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-09-24%252C2029-01-01%2C253%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C254%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-12-21%252C2029-01-01%2C255%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-06-17%252C2029-01-01%2C257%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C259%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C260%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C261%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-06-17%252C2029-01-01%2C262%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-10-14%252C2029-01-01%2C263%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-12-21%252C2029-01-01%2C265%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-10-14%252C2029-01-01%2C266%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2017-12-13%252C2029-01-01%2C267%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2017-12-13%252C2029-01-01%2C401%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-09-05%252C2029-01-01%2C402%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C403%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-10-03%252C2029-01-01%2C404%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-11-13%252C2029-01-01%2C405%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-01-08%252C2029-01-01%2C407%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-10-22%252C2029-01-01%2C408%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C409%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C410%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-01-31%252C2029-01-01%2C411%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C412%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C413%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-06-17%252C2029-01-01%2C414%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C415%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-06-10%252C2029-01-01%2C416%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C417%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C418%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C419%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C420%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C421%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-06-10%252C2029-01-01%2C422%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-04-08%252C2029-01-01%2C423%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C424%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-03-12%252C2029-01-01%2C425%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2011-12-15%252C2029-01-01%2C426%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2011-12-15%252C2029-01-01%2C427%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2011-12-15%252C2029-01-01%2C428%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C429%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-08-07%252C2029-01-01%2C430%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-01-31%252C2029-01-01%2C431%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-05-28%252C2029-01-01%2C432%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-05-27%252C2029-01-01%2C433%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C434%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-07-15%252C2029-01-01%2C435%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-10-14%252C2029-01-01%2C436%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-09-27%252C2029-01-01%2C437%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-09-27%252C2029-01-01%2C438%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C439%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-12-04%252C2029-01-01%2C440%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-12-04%252C2029-01-01%2C441%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C442%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C443%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-05-23%252C2029-01-01%2C600%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C601%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-02-10%252C2029-01-01%2C602%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C603%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C604%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C605%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-04-08%252C2029-01-01%2C607%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C608%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-05-27%252C2029-01-01%2C609%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-05-13%252C2029-01-01%2C610%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-05-27%252C2029-01-01%2C611%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C612%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-08-07%252C2029-01-01%2C613%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C614%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-05-27%252C2029-01-01%2C615%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-12-20%252C2029-01-01%2C616%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C617%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C618%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-07-15%252C2029-01-01%2C619%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C620%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C621%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C622%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-12-20%252C2029-01-01%2C623%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-05-13%252C2029-01-01%2C624%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-07-15%252C2029-01-01%2C625%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-08-07%252C2029-01-01%2C626%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C627%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-03-12%252C2029-01-01%2C628%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C629%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-02-10%252C2029-01-01%2C630%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-09-18%252C2029-01-01%2C631%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C637%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-03-12%252C2029-01-01%2C638%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C639%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-05-27%252C2029-01-01%2C640%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-12-04%252C2029-01-01%2C641%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-12-04%252C2029-01-01%2C642%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-09-18%252C2029-01-01%2C710%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C722%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-07-16%252C2029-01-01%2C723%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-07-16%252C2029-01-01%2C724%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C725%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-01-08%252C2029-01-01%2C726%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-10-22%252C2029-01-01%2C727%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-05-11%252C2029-01-01%2C728%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-12-03%252C2029-01-01%2C729%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-06-16%252C2029-01-01%2C730%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C731%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-12-03%252C2029-01-01%2C732%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C733%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-01-08%252C2029-01-01%2C734%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-10-22%252C2029-01-01%2C736%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C737%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-12-03%252C2029-01-01%2C738%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-01-08%252C2029-01-01%2C739%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-08-07%252C2029-01-01%2C740%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C832%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C900%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2020-04-11%252C2029-01-01 \ No newline at end of file diff --git a/titles/diva/data/PvList3.dat b/titles/diva/data/PvList3.dat new file mode 100644 index 0000000..f2bdfaf --- /dev/null +++ b/titles/diva/data/PvList3.dat @@ -0,0 +1 @@ +1%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C1%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C2%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C2%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C3%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-08-07%252C2029-01-01%2C3%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2014-08-07%252C2029-01-01%2C4%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C5%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C5%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C6%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-08-07%252C2029-01-01%2C6%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2014-08-07%252C2029-01-01%2C7%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-12-03%252C2029-01-01%2C7%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-12-03%252C2029-01-01%2C8%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-09-05%252C2029-01-01%2C8%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2014-09-05%252C2029-01-01%2C9%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C10%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-01-08%252C2029-01-01%2C10%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2016-01-08%252C2029-01-01%2C11%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-05-23%252C2029-01-01%2C11%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2014-05-23%252C2029-01-01%2C12%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C13%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-08-07%252C2029-01-01%2C13%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2014-08-07%252C2029-01-01%2C14%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-10-03%252C2029-01-01%2C14%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2014-10-03%252C2029-01-01%2C15%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-10-03%252C2029-01-01%2C15%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2014-10-03%252C2029-01-01%2C16%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C17%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-07-16%252C2029-01-01%2C17%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-07-16%252C2029-01-01%2C18%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C19%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C19%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C20%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C21%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C22%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C23%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C23%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C24%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-10-03%252C2029-01-01%2C24%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2014-10-03%252C2029-01-01%2C25%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C27%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C27%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C28%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-08-07%252C2029-01-01%2C28%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-08-07%252C2029-01-01%2C29%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-10-14%252C2029-01-01%2C29%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2016-10-14%252C2029-01-01%2C30%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-10-22%252C2029-01-01%2C30%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-10-22%252C2029-01-01%2C31%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C31%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C32%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-11-13%252C2029-01-01%2C32%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2014-11-13%252C2029-01-01%2C37%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-06-06%252C2029-01-01%2C37%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2014-06-06%252C2029-01-01%2C38%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-05-13%252C2029-01-01%2C38%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-05-13%252C2029-01-01%2C39%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C39%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C40%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2011-10-18%252C2029-01-01%2C40%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2011-10-18%252C2029-01-01%2C41%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C42%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-02-03%252C2029-01-01%2C43%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C43%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C44%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C45%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C45%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C46%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C46%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C47%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2011-11-30%252C2029-01-01%2C48%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C49%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-06-17%252C2029-01-01%2C49%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2016-06-17%252C2029-01-01%2C50%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C50%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C51%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-18%252C2029-01-01%2C51%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2016-02-18%252C2029-01-01%2C52%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C52%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C53%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-09-24%252C2029-01-01%2C53%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-09-24%252C2029-01-01%2C54%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C54%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C55%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C55%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C56%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C57%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-12-03%252C2029-01-01%2C57%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-12-03%252C2029-01-01%2C58%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C58%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C59%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C59%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C60%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-04-08%252C2029-01-01%2C60%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-04-08%252C2029-01-01%2C61%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-02-03%252C2029-01-01%2C61%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2012-02-03%252C2029-01-01%2C62%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-07-16%252C2029-01-01%2C62%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-07-16%252C2029-01-01%2C63%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C63%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C64%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-05-13%252C2029-01-01%2C64%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-05-13%252C2029-01-01%2C65%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-03-12%252C2029-01-01%2C65%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-03-12%252C2029-01-01%2C66%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-11-13%252C2029-01-01%2C66%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2014-11-13%252C2029-01-01%2C67%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C68%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C79%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-10-26%252C2029-01-01%2C81%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C81%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C82%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C82%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C83%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-10-26%252C2029-01-01%2C84%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-10-14%252C2029-01-01%2C84%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2016-10-14%252C2029-01-01%2C85%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-12-03%252C2029-01-01%2C85%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-12-03%252C2029-01-01%2C86%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-10-26%252C2029-01-01%2C87%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-04-08%252C2029-01-01%2C87%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-04-08%252C2029-01-01%2C88%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C88%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C89%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-10-14%252C2029-01-01%2C89%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2016-10-14%252C2029-01-01%2C90%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-07-16%252C2029-01-01%2C90%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-07-16%252C2029-01-01%2C91%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-10-26%252C2029-01-01%2C92%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-10-26%252C2029-01-01%2C93%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-01-31%252C2029-01-01%2C94%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-09-24%252C2029-01-01%2C94%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-09-24%252C2029-01-01%2C95%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-10-26%252C2029-01-01%2C96%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-18%252C2029-01-01%2C96%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2016-02-18%252C2029-01-01%2C97%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-10-26%252C2029-01-01%2C101%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-09-05%252C2029-01-01%2C101%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2014-09-05%252C2029-01-01%2C102%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C103%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-08-31%252C2029-01-01%2C104%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-08-31%252C2029-01-01%2C201%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C201%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C202%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C202%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C203%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-01-31%252C2029-01-01%2C204%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C205%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C206%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C207%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C208%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-09-05%252C2029-01-01%2C208%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2014-09-05%252C2029-01-01%2C209%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-05-13%252C2029-01-01%2C209%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-05-13%252C2029-01-01%2C210%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C210%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C211%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C211%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C212%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-10-22%252C2029-01-01%2C212%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-10-22%252C2029-01-01%2C213%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2011-11-30%252C2029-01-01%2C214%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-08-31%252C2029-01-01%2C215%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-05-28%252C2029-01-01%2C216%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-08-31%252C2029-01-01%2C218%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-08-31%252C2029-01-01%2C219%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-06-10%252C2029-01-01%2C219%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-06-10%252C2029-01-01%2C220%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-08-31%252C2029-01-01%2C221%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-10-14%252C2029-01-01%2C221%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2016-10-14%252C2029-01-01%2C222%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-08-31%252C2029-01-01%2C223%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-12-20%252C2029-01-01%2C224%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-01-08%252C2029-01-01%2C224%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2016-01-08%252C2029-01-01%2C225%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-10-22%252C2029-01-01%2C226%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-11-13%252C2029-01-01%2C226%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2014-11-13%252C2029-01-01%2C227%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-08-31%252C2029-01-01%2C228%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-08-31%252C2029-01-01%2C231%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-08-07%252C2029-01-01%2C231%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-08-07%252C2029-01-01%2C232%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-06-17%252C2029-01-01%2C232%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2016-06-17%252C2029-01-01%2C233%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C234%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C234%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C235%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-12-20%252C2029-01-01%2C236%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-12-20%252C2029-01-01%2C238%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-02-10%252C2029-01-01%2C239%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-12-20%252C2029-01-01%2C240%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-12-20%252C2029-01-01%2C241%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C242%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-08-07%252C2029-01-01%2C243%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-07-16%252C2029-01-01%2C244%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C246%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-04-08%252C2029-01-01%2C247%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C248%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-08-07%252C2029-01-01%2C249%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C250%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-05-13%252C2029-01-01%2C251%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-09-24%252C2029-01-01%2C253%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C254%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-12-21%252C2029-01-01%2C255%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-06-17%252C2029-01-01%2C257%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C259%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C260%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C261%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-06-17%252C2029-01-01%2C262%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-10-14%252C2029-01-01%2C263%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-12-21%252C2029-01-01%2C265%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-10-14%252C2029-01-01%2C266%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2017-12-13%252C2029-01-01%2C267%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2017-12-13%252C2029-01-01%2C401%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-09-05%252C2029-01-01%2C401%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2014-09-05%252C2029-01-01%2C402%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C402%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C403%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-10-03%252C2029-01-01%2C403%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2014-10-03%252C2029-01-01%2C404%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-11-13%252C2029-01-01%2C404%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2014-11-13%252C2029-01-01%2C405%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-01-08%252C2029-01-01%2C405%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2016-01-08%252C2029-01-01%2C407%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-10-22%252C2029-01-01%2C407%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-10-22%252C2029-01-01%2C408%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C409%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C410%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-01-31%252C2029-01-01%2C411%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C412%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C413%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-06-17%252C2029-01-01%2C413%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2016-06-17%252C2029-01-01%2C414%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C414%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C415%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-06-10%252C2029-01-01%2C415%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-06-10%252C2029-01-01%2C416%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C417%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C418%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C419%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C420%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C421%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-06-10%252C2029-01-01%2C421%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-06-10%252C2029-01-01%2C422%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-04-08%252C2029-01-01%2C422%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-04-08%252C2029-01-01%2C423%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C423%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C424%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-03-12%252C2029-01-01%2C424%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-03-12%252C2029-01-01%2C425%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2011-12-15%252C2029-01-01%2C426%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2011-12-15%252C2029-01-01%2C427%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2011-12-15%252C2029-01-01%2C428%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C428%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2016-04-08%252C2029-01-01%2C429%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-08-07%252C2029-01-01%2C429%252C1%252C1%252C2000-01-01%252C2029-01-01%252C2015-08-07%252C2029-01-01%2C430%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-01-31%252C2029-01-01%2C431%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-05-28%252C2029-01-01%2C432%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-05-27%252C2029-01-01%2C433%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C434%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-07-15%252C2029-01-01%2C435%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-10-14%252C2029-01-01%2C436%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-09-27%252C2029-01-01%2C437%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2012-09-27%252C2029-01-01%2C438%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-02-12%252C2029-01-01%2C439%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-12-04%252C2029-01-01%2C440%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-12-04%252C2029-01-01%2C441%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C442%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C443%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-05-23%252C2029-01-01%2C600%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C601%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-02-10%252C2029-01-01%2C602%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C603%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C604%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C605%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-04-08%252C2029-01-01%2C607%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C608%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-05-27%252C2029-01-01%2C609%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-05-13%252C2029-01-01%2C610%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-05-27%252C2029-01-01%2C611%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C612%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-08-07%252C2029-01-01%2C613%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C614%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-05-27%252C2029-01-01%2C615%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-12-20%252C2029-01-01%2C616%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C617%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C618%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-07-15%252C2029-01-01%2C619%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C620%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C621%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C622%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2013-12-20%252C2029-01-01%2C623%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-05-13%252C2029-01-01%2C624%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-07-15%252C2029-01-01%2C625%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-08-07%252C2029-01-01%2C626%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C627%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-03-12%252C2029-01-01%2C628%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C629%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-02-10%252C2029-01-01%2C630%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-09-18%252C2029-01-01%2C631%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C637%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-03-12%252C2029-01-01%2C638%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2010-11-25%252C2029-01-01%2C639%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-05-27%252C2029-01-01%2C640%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-12-04%252C2029-01-01%2C641%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-12-04%252C2029-01-01%2C642%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2014-09-18%252C2029-01-01%2C710%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-02-12%252C2029-01-01%2C722%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-07-16%252C2029-01-01%2C723%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-07-16%252C2029-01-01%2C724%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C725%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-01-08%252C2029-01-01%2C726%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-10-22%252C2029-01-01%2C727%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-05-11%252C2029-01-01%2C728%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-12-03%252C2029-01-01%2C729%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-06-16%252C2029-01-01%2C730%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-01-08%252C2029-01-01%2C731%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-12-03%252C2029-01-01%2C732%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C733%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-01-08%252C2029-01-01%2C734%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-10-22%252C2029-01-01%2C736%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C737%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-12-03%252C2029-01-01%2C738%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-01-08%252C2029-01-01%2C739%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2015-08-07%252C2029-01-01%2C740%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01%2C832%252C1%252C0%252C2000-01-01%252C2029-01-01%252C2016-08-08%252C2029-01-01 \ No newline at end of file diff --git a/titles/diva/data/PvList4.dat b/titles/diva/data/PvList4.dat new file mode 100644 index 0000000..cf07be9 --- /dev/null +++ b/titles/diva/data/PvList4.dat @@ -0,0 +1 @@ +%2A%2A%2A \ No newline at end of file diff --git a/titles/diva/data/QuestInfo.dat b/titles/diva/data/QuestInfo.dat new file mode 100644 index 0000000..702a9d1 --- /dev/null +++ b/titles/diva/data/QuestInfo.dat @@ -0,0 +1,60 @@ +1001,1,0,0,2022-06-18 09:00:00.0,2022-06-20 09:00:00.0,Play in the Simple Mode or Contest Mode,1,1,1 +1010,10,0,0,2022-06-18 09:00:00.0,2022-06-20 09:00:00.0,Play with a randomly selected Miku module,1,1,1 +1011,11,0,0,2022-06-18 09:00:00.0,2022-06-20 09:00:00.0,Hit COOL 500 times or more,1,3,1 +1012,12,0,0,2022-06-18 09:00:00.0,2022-06-20 09:00:00.0,Take a photo in the PV watching mode,1,1,1 +1013,13,0,0,2022-06-18 09:00:00.0,2022-06-20 09:00:00.0,Play with the SE turned OFF,1,1,1 +1002,2,0,0,2022-06-18 09:00:00.0,2022-06-20 09:00:00.0,Do some Max Chain Slides,1,1,1 +1003,3,0,0,2022-06-18 09:00:00.0,2022-06-20 09:00:00.0,Challenge yourself to beat a trial,1,1,1 +1004,4,0,0,2022-06-18 09:00:00.0,2022-06-20 09:00:00.0,Play a song on ,1,2,1 +1005,5,0,0,2022-06-18 09:00:00.0,2022-06-20 09:00:00.0,Play a chart by Misora,1,2,1 +1006,6,0,0,2022-06-18 09:00:00.0,2022-06-20 09:00:00.0,Score at least 390390 pts in any song,1,1,1 +1007,7,0,0,2022-06-18 09:00:00.0,2022-06-20 09:00:00.0,Play a song chosen at random,1,1,1 +1008,8,0,0,2022-06-18 09:00:00.0,2022-06-20 09:00:00.0,Play with at least 100 percent result,1,1,1 +1009,9,0,0,2022-06-18 09:00:00.0,2022-06-20 09:00:00.0,Play with the 169 module,1,1,1 +3001,1,2,0,2001-01-01 09:00:00.0,2100-01-01 08:59:59.0,Clear a song with the HISPEED mode,1,2,1 +3002,2,2,0,2001-01-01 09:00:00.0,2100-01-01 08:59:59.0,Clear a song with the HIDDEN mode,1,2,1 +3003,3,2,0,2001-01-01 09:00:00.0,2100-01-01 08:59:59.0,Clear a song with the SUDDEN mode,1,2,1 +3004,4,2,0,2001-01-01 09:00:00.0,2100-01-01 08:59:59.0,Enhance your Chain-Perfect Trial streak,1,4,1 +3005,5,2,0,2001-01-01 09:00:00.0,2100-01-01 08:59:59.0,Beat your rival in a song by percentage,1,4,1 +2001,1,1,0,2022-06-18 09:00:00.0,2022-06-25 09:00:00.0,Hit COOL at least 1300 times,1,3,1 +2002,2,1,0,2022-06-18 09:00:00.0,2022-06-25 09:00:00.0,Hit COOL at least 2000 times,1,3,1 +2003,3,1,0,2022-06-18 09:00:00.0,2022-06-25 09:00:00.0,Achieve a sum of MAX COMBO of 730 or more,1,3,1 +2004,4,1,0,2022-06-18 09:00:00.0,2022-06-25 09:00:00.0,Achieve a sum of MAX COMBO of 960 or more,1,3,1 +2005,5,1,0,2022-06-18 09:00:00.0,2022-06-25 09:00:00.0,Score at least 440000 pts in any song,1,1,1 +*** +*** +*** +*** +*** +*** +*** +*** +*** +*** +*** +*** +*** +*** +*** +*** +*** +*** +*** +*** +*** +*** +*** +*** +*** +*** +*** +*** +*** +*** +*** +*** +*** +*** +*** +*** +*** diff --git a/titles/diva/data/ShopCatalog.dat b/titles/diva/data/ShopCatalog.dat new file mode 100644 index 0000000..6274caf --- /dev/null +++ b/titles/diva/data/ShopCatalog.dat @@ -0,0 +1,401 @@ +0,0,åˆéŸ³ãƒŸã‚¯,0,2009-01-01,2029-01-01,1 +1,0,弱音ãƒã‚¯,50,2009-01-01,2029-01-01,1 +2,0,亞北ãƒãƒ«,50,2009-01-01,2029-01-01,4 +3,0,メイコスタイル,100,2009-01-01,2029-01-01,2 +4,0,リンスタイル,100,2009-01-01,2029-01-01,3 +5,0,スペãƒãƒ£ãƒ³5,100,2009-01-01,2029-01-01,4 +6,0,スペãƒãƒ£ãƒ³39,50,2009-01-01,2029-01-01,5 +7,0,ガリアè»ç¬¬7å°éšŠ,100,2009-01-01,2029-01-01,6 +8,0,P-スタイルPB,50,2009-01-01,2029-01-01,31 +9,0,P-スタイルCW,50,2009-01-01,2029-01-01,32 +10,0,P-スタイルIS,50,2009-01-01,2029-01-01,33 +11,0,P-スタイルRP,50,2009-01-01,2029-01-01,34 +12,0,P-スタイルLP,50,2009-01-01,2029-01-01,35 +13,0,P-スタイルFB,50,2009-01-01,2029-01-01,36 +14,0,P-スタイルMG,50,2009-01-01,2029-01-01,37 +15,0,P-スタイルCG,300,2009-01-01,2029-01-01,38 +16,0,ãƒã‚¢,50,2009-01-01,2029-01-01,7 +17,0,プラグイン,100,2009-01-01,2029-01-01,8 +18,0,ゴシック,100,2009-01-01,2029-01-01,9 +19,0,プリンセス,50,2009-01-01,2029-01-01,10 +20,0,ミコ,100,2009-01-01,2029-01-01,11 +21,0,ã«ã‚ƒã‚“ã“,50,2009-01-01,2029-01-01,12 +22,0,ã­ã‚€ã­ã‚€,100,2009-01-01,2029-01-01,13 +23,0,ãƒãƒ¼ãƒˆãƒãƒ³ã‚¿ãƒ¼,100,2009-01-01,2029-01-01,14 +24,0,ボーカル,50,2009-01-01,2029-01-01,15 +25,0,パンク,100,2009-01-01,2029-01-01,16 +26,0,ダンサー,50,2009-01-01,2029-01-01,17 +27,0,スター,100,2009-01-01,2029-01-01,18 +28,0,フェアリー,100,2009-01-01,2029-01-01,19 +29,0,スクール,100,2009-01-01,2029-01-01,20 +30,0,スノウ,50,2009-01-01,2029-01-01,21 +31,0,アラビアン,100,2009-01-01,2029-01-01,22 +32,0,ã¿ã‚„ã³,50,2009-01-01,2029-01-01,23 +33,0,ãƒãƒ£ã‚¤ãƒŠ,300,2009-01-01,2029-01-01,24 +34,0,マジシャン,50,2009-01-01,2029-01-01,25 +35,0,ホワイトドレス,100,2009-01-01,2029-01-01,26 +36,0,パイレーツ,50,2009-01-01,2029-01-01,27 +37,0,VN02,100,2009-01-01,2029-01-01,28 +38,0,ギャラクシー,100,2009-01-01,2029-01-01,29 +39,0,ãƒãƒ„ãƒãƒŸã‚¯,100,2009-01-01,2029-01-01,30 +40,0,é¡éŸ³ãƒªãƒ³,50,2009-01-01,2029-01-01,1 +41,0,é¡éŸ³ãƒ¬ãƒ³,50,2009-01-01,2029-01-01,1 +42,0,巡音ルカ,50,2009-01-01,2029-01-01,1 +43,0,咲音メイコ,300,2009-01-01,2029-01-01,7 +44,0,メイコ,50,2009-01-01,2029-01-01,1 +45,0,カイト,50,2009-01-01,2029-01-01,1 +46,0,åˆéŸ³ãƒŸã‚¯ スイムウェアS,1000,2009-01-01,2029-01-01,140 +47,0,åˆéŸ³ãƒŸã‚¯ スイムウェア,1000,2009-01-01,2029-01-01,141 +48,0,é¡éŸ³ãƒªãƒ³ スイムウェア,1000,2009-01-01,2029-01-01,39 +49,0,é¡éŸ³ãƒ¬ãƒ³ スイムウェア,300,2009-01-01,2029-01-01,33 +50,0,巡音ルカ スイムウェア,1000,2009-01-01,2029-01-01,31 +51,0,メイコ スイムウェア,1000,2009-01-01,2029-01-01,24 +52,0,カイト スイムウェア,300,2009-01-01,2029-01-01,31 +53,0,åˆéŸ³ãƒŸã‚¯ アペンド,300,2010-08-31,2029-01-01,152 +54,0,ホワイトワンピース,100,2010-11-16,2029-01-01,49 +55,0,ナãƒãƒ¥ãƒ©ãƒ«,200,2010-11-16,2029-01-01,48 +56,0,スピリãƒãƒ¥ã‚¢ãƒ«,200,2010-11-16,2029-01-01,41 +57,0,カラフルドロップ,200,2010-12-27,2029-01-01,57 +58,0,åˆéŸ³ãƒŸã‚¯ è¶,100,2010-11-16,2029-01-01,40 +59,0,ãƒã‚¢ãƒ•ãƒ«ã‚­ãƒ£ãƒ³ãƒ‡ã‚£,200,2010-12-27,2029-01-01,6 +60,0,スクールジャージ,100,2010-12-27,2029-01-01,3 +61,0,巡音ルカ è¯,100,2010-11-16,2029-01-01,2 +62,0,åˆéŸ³ãƒŸã‚¯ クリスマス,150,2010-11-30,2029-01-01,153 +63,0,é¡éŸ³ãƒªãƒ³ クリスマス,150,2010-11-30,2029-01-01,50 +64,0,é¡éŸ³ãƒ¬ãƒ³ クリスマス,150,2010-11-30,2029-01-01,41 +65,0,巡音ルカ クリスマス,150,2010-11-30,2029-01-01,40 +66,0,メイコ クリスマス,150,2010-11-30,2029-01-01,32 +67,0,カイト クリスマス,150,2010-11-30,2029-01-01,41 +68,0,雪ミク 2010,300,2011-01-31,2029-01-01,154 +69,0,ヴィンテージドレス,200,2011-02-17,2029-01-01,39 +70,0,ピンクãƒãƒƒãƒ—ス,100,2011-01-31,2029-01-01,55 +71,0,ピンクãƒãƒƒãƒ—ス AS,100,2011-01-31,2029-01-01,56 +72,0,リアクター,200,2011-02-24,2029-01-01,3 +73,0,パンキッシュ,200,2011-02-24,2029-01-01,2 +74,0,ãƒãƒ¼ãƒ‰ãƒ­ãƒƒã‚¯,200,2011-01-31,2029-01-01,5 +75,0,クラシック,200,2011-02-17,2029-01-01,2 +76,0,スカーレット,200,2011-02-17,2029-01-01,3 +77,0,雪ミク 2011,300,2011-01-31,2029-01-01,155 +78,0,ãŠã•ã‚“ã½ã‚¹ã‚¿ã‚¤ãƒ«,150,2011-07-26,2029-01-01,42 +79,0,ã¿ããšãã‚“,150,2011-05-20,2029-01-01,43 +80,0,イエロー,150,2011-11-01,2029-01-01,44 +81,0,ジャー★ジ,150,2011-09-08,2029-01-01,45 +82,0,ノーブル,150,2011-05-20,2029-01-01,46 +83,0,パウダー,150,2011-09-08,2029-01-01,47 +84,0,エールダンジュ,150,2011-11-01,2029-01-01,50 +85,0,スペイシーナース,200,2012-03-13,2029-01-01,51 +86,0,åˆéŸ³ãƒŸã‚¯ キュート,200,2011-07-26,2029-01-01,52 +87,0,エンジェル,200,2012-01-10,2029-01-01,53 +88,0,サイãƒãƒ†ãƒŸã‚¯,150,2011-07-26,2029-01-01,54 +89,0,∞,200,2012-02-14,2029-01-01,58 +90,0,åˆéŸ³ãƒŸã‚¯ スイムウェアB,1000,2011-07-26,2029-01-01,143 +91,0,アシンメトリーR,200,2011-09-08,2029-01-01,2 +92,0,EoEスタイル,200,2011-11-01,2029-01-01,4 +93,0,é¡éŸ³ãƒªãƒ³ キュート,200,2011-07-26,2029-01-01,5 +94,0,é¡éŸ³ãƒªãƒ³ スイムウェアT,1000,2011-07-26,2029-01-01,41 +95,0,アシンメトリーL,200,2011-09-08,2029-01-01,4 +96,0,é¡éŸ³ãƒ¬ãƒ³ スイムウェアWS,750,2011-07-26,2029-01-01,35 +97,0,シフォンワンピース,150,2011-06-21,2029-01-01,3 +98,0,フロイライン,200,2011-09-08,2029-01-01,4 +99,0,VFスーツ,200,2012-01-10,2029-01-01,6 +100,0,巡音ルカ スイムウェアP,1000,2011-07-26,2029-01-01,32 +101,0,キャンパス,200,2012-02-14,2029-01-01,3 +102,0,ãƒã‚³ã‚µã‚¤ãƒãƒ¼,300,2011-06-21,2029-01-01,4 +103,0,カイト スイムウェアV,750,2011-07-26,2029-01-01,32 +104,0,カイト スイムウェアV AS,750,2011-07-26,2029-01-01,34 +105,0,ãµã‚ãµã‚コート,200,2011-11-01,2029-01-01,2 +106,0,モダンガール,150,2012-03-13,2029-01-01,4 +107,0,モダンガール AS,150,2012-03-13,2029-01-01,5 +108,0,メイコ スイムウェアB,1000,2011-07-26,2029-01-01,26 +109,0,エスニック,200,2011-05-20,2029-01-01,5 +110,0,亞北ãƒãƒ« スイムウェア,1000,2011-07-26,2029-01-01,12 +111,0,サイãƒãƒ¼ãƒ€ã‚¤ãƒ–,200,2011-05-20,2029-01-01,2 +112,0,弱音ãƒã‚¯ スイムウェア,1000,2011-07-26,2029-01-01,11 +113,0,ブラックワンピース,300,2011-05-20,2029-01-01,8 +114,0,ブラックワンピース NS,300,2011-05-20,2029-01-01,9 +115,0,咲音メイコ スイムウェア,1000,2011-07-26,2029-01-01,13 +116,0,メイコ 大正浪漫,250,2011-11-01,2029-01-01,30 +117,0,é¡éŸ³ãƒªãƒ³ スクールウェア,250,2011-12-06,2029-01-01,45 +118,0,é¡éŸ³ãƒ¬ãƒ³ スクールウェア,250,2011-12-06,2029-01-01,40 +119,0,巡音ルカ 魔女ã£å¨˜Style,250,2012-01-31,2029-01-01,37 +120,0,カイト ホワイトブレザー,250,2012-02-14,2029-01-01,38 +121,0,フェアリーマカロン,300,2011-06-21,2029-01-01,41 +122,0,セクシープディング,300,2011-06-21,2029-01-01,33 +124,0,ホワイト・イヴ,250,2012-11-05,2029-01-01,61 +125,0,Hello World.,250,2012-10-04,2029-01-01,68 +126,0,レーシングミク2010ver.,200,2012-11-22,2029-01-01,62 +127,0,レーシングミク2011ver.,250,2012-12-06,2029-01-01,63 +128,0,フェイ・イェン スタイル,250,2012-11-22,2029-01-01,66 +129,0,回転少女,250,2012-11-05,2029-01-01,71 +130,0,ラセツトムクロ,250,2012-05-24,2029-01-01,69 +131,0,オービット,250,2012-11-05,2029-01-01,60 +132,0,パッãƒãƒ¯ãƒ¼ã‚¯,250,2012-04-26,2029-01-01,65 +133,0,ソニックスタイル,250,2012-08-31,2029-01-01,59 +134,0,ãƒãƒ­ãƒ«,250,2012-10-04,2029-01-01,64 +135,0,コンフリクト,200,2012-04-26,2029-01-01,70 +136,0,シャイニー,250,2012-07-05,2029-01-01,72 +137,0,TYPE2020,250,2012-08-31,2029-01-01,67 +138,0,部活少女,250,2012-07-31,2029-01-01,6 +139,0,ゴシック・パープル,250,2012-07-31,2029-01-01,3 +141,0,é¡éŸ³ãƒªãƒ³ アペンド,300,2011-12-27,2029-01-01,51 +142,0,ãƒãƒ¼ãƒ ãƒ¬ã‚¹ No.1,200,2012-12-27,2029-01-01,8 +143,0,レーシングリン2010ver.,200,2012-11-22,2029-01-01,12 +144,0,ブラックスター,250,2012-09-14,2029-01-01,7 +145,0,陽炎,250,2012-04-26,2029-01-01,11 +146,0,é¡éŸ³ãƒªãƒ³ 蘇芳,150,2012-04-05,2029-01-01,9 +147,0,é¡éŸ³ãƒ¬ãƒ³ アペンド,300,2011-12-27,2029-01-01,42 +148,0,ブルームーン,250,2012-09-14,2029-01-01,5 +149,0,é¡éŸ³ãƒ¬ãƒ³ è—鉄,150,2012-04-05,2029-01-01,6 +150,0,ストレンジダーク,250,2012-07-05,2029-01-01,8 +151,0,ãƒãƒ¼ãƒ ãƒ¬ã‚¹ No.7,200,2012-12-27,2029-01-01,9 +153,0,サイレンス,250,2012-07-05,2029-01-01,7 +154,0,レーシングルカ2010ver.,200,2012-11-22,2029-01-01,10 +155,0,サイãƒãƒ¼ãƒã‚¤ã‚·ãƒ§ãƒ³,300,2011-12-15,2029-01-01,42 +156,0,ナギサ・レプカ,200,2012-10-04,2029-01-01,8 +157,0,ナギサ・レプカ AS,200,2012-10-04,2029-01-01,9 +158,0,時雨,250,2012-05-24,2029-01-01,6 +159,0,スミレ,250,2012-07-05,2029-01-01,5 +160,0,VFニンジャ,200,2013-02-15,2029-01-01,7 +161,0,VFニンジャ AS,200,2013-02-15,2029-01-01,8 +162,0,怪盗ブラックテール,250,2012-11-05,2029-01-01,6 +163,0,紅葉,250,2012-05-24,2029-01-01,8 +164,0,レーシングメイコ2010ver.,200,2012-11-22,2029-01-01,9 +165,0,ローレライ,250,2012-08-31,2029-01-01,7 +166,0,ノスタルジー,250,2012-07-31,2029-01-01,10 +167,0,雪ミク 2012,300,2011-12-19,2029-01-01,156 +168,0,AMERICANA,300,2012-07-31,2029-01-01,167 +169,0,桜ミク,300,2012-04-05,2029-01-01,168 +170,0,巡音ルカ コンフリクト,300,2012-04-26,2029-01-01,43 +171,0,é¡éŸ³ãƒªãƒ³ 蘇芳 妖ç‹,200,2012-04-05,2029-01-01,10 +172,0,é¡éŸ³ãƒ¬ãƒ³ è—鉄 妖ç‹,200,2012-04-05,2029-01-01,7 +188,0,ドリーマー,250,2013-01-30,2029-01-01,120 +189,0,åˆéŸ³ãƒŸã‚¯ 妄想ガール,250,2012-12-06,2029-01-01,121 +190,0,雪ミク 2013,300,2012-12-17,2029-01-01,157 +191,0,é¡éŸ³ãƒªãƒ³ 妄想ガール,250,2012-12-06,2029-01-01,29 +192,0,ロジカリスト,250,2012-12-06,2029-01-01,25 +193,0,オンザロック,250,2013-01-30,2029-01-01,22 +194,0,雪ミク 2013 AS,300,2012-12-17,2029-01-01,158 +195,0,ディープスカイ,250,2014-02-25,2029-01-01,73 +196,0,ç´«æšç¾½,250,2015-04-23,2029-01-01,74 +197,0,メモリア,250,2015-03-26,2029-01-01,75 +198,0,ç†ç³»å°‘女,250,2014-06-10,2029-01-01,76 +199,0,ピエレッタ,250,2013-09-25,2029-01-01,77 +200,0,イノセント,250,2014-04-17,2029-01-01,78 +201,0,堕悪天使,250,2014-04-17,2029-01-01,79 +202,0,サマーメモリー,250,2014-08-28,2029-01-01,80 +203,0,åˆéŸ³ãƒŸã‚¯ 翠玉,250,2016-05-26,2029-01-01,81 +204,0,ソリãƒãƒ¥ãƒ¼ãƒ‰,250,2015-04-23,2029-01-01,82 +205,0,ホーリーゴッデス,250,2015-05-28,2029-01-01,83 +206,0,フォニュエールスタイル,250,2014-07-29,2029-01-01,84 +207,0,ã­ã“ã­ã“ケープ,250,2014-06-10,2029-01-01,85 +208,0,アジテーション,250,2013-09-25,2029-01-01,86 +209,0,スターヴォイス,200,2013-09-25,2029-01-01,87 +210,0,ãƒãƒ¼ãƒˆãƒ“ート,250,2014-09-17,2029-01-01,89 +211,0,パンジー,250,2013-09-25,2029-01-01,90 +212,0,レーシングミク2012ver.,250,2013-03-12,2029-01-01,91 +213,0,ã‚ãŒã¾ã¾å·¥å ´é•·,250,2015-05-28,2029-01-01,92 +214,0,Hello,Good night.,250,2014-02-25,2029-01-01,93 +215,0,åˆéŸ³ãƒŸã‚¯ ã¿ãšãŸã¾ãƒ“キニ,1000,2013-07-04,2029-01-01,145 +216,0,åˆéŸ³ãƒŸã‚¯ スクール競泳,1000,2013-06-06,2029-01-01,144 +217,0,åˆéŸ³ãƒŸã‚¯ 浴衣スタイル,200,2013-07-31,2029-01-01,94 +218,0,らんã¿ã‚“ã,250,2013-04-26,2029-01-01,95 +219,0,リボンガール,250,2013-01-30,2029-01-01,96 +220,0,メランコリー,250,2014-01-15,2029-01-01,13 +221,0,トランスミッター,250,2014-04-17,2029-01-01,14 +222,0,é¡éŸ³ãƒªãƒ³ 桜月,250,2013-12-10,2029-01-01,15 +223,0,é¡éŸ³ãƒªãƒ³ 雨,250,2014-07-29,2029-01-01,16 +224,0,é¡éŸ³ãƒªãƒ³ ã—ã¾ã—ã¾ãƒ“キニ,1000,2013-07-04,2029-01-01,42 +225,0,é¡éŸ³ãƒªãƒ³ SW スクール,1000,2013-06-06,2029-01-01,43 +226,0,é¡éŸ³ãƒªãƒ³ 浴衣スタイル,150,2013-07-31,2029-01-01,17 +227,0,魔導師ã®ã‚¿ãƒžã‚´,250,2013-09-11,2029-01-01,19 +228,0,スタイリッシュエナジーR,250,2013-04-05,2029-01-01,20 +229,0,トラッドスクール,250,2012-12-27,2029-01-01,21 +230,0,スターマイン,250,2013-09-25,2029-01-01,10 +231,0,レシーãƒãƒ¼,250,2014-04-17,2029-01-01,11 +232,0,é¡éŸ³ãƒ¬ãƒ³ 鳳月,250,2014-07-29,2029-01-01,12 +233,0,é¡éŸ³ãƒ¬ãƒ³ 鶴,250,2013-12-10,2029-01-01,13 +234,0,ãƒãƒƒãƒ‰ãƒœãƒ¼ã‚¤,200,2013-09-25,2029-01-01,14 +235,0,é¡éŸ³ãƒ¬ãƒ³ SW ボクサー,1000,2013-06-06,2029-01-01,36 +236,0,é¡éŸ³ãƒ¬ãƒ³ 浴衣スタイル,200,2013-07-31,2029-01-01,16 +237,0,スタイリッシュエナジーL,250,2013-04-05,2029-01-01,17 +238,0,生徒会執行部,250,2012-12-27,2029-01-01,18 +239,0,ãƒãƒƒãƒ‰ãƒœãƒ¼ã‚¤ AS,200,2013-09-25,2029-01-01,15 +240,0,ゆるãµã‚コーデ,250,2014-06-25,2029-01-01,11 +241,0,エターナルホワイト,250,2013-12-10,2029-01-01,12 +242,0,アムール,250,2014-04-17,2029-01-01,13 +243,0,巡音ルカ 紅玉,250,2016-05-26,2029-01-01,14 +244,0,巡音ルカ リゾートビキニ,1000,2013-07-04,2029-01-01,34 +245,0,巡音ルカ 競泳タイプ,1000,2013-06-06,2029-01-01,35 +246,0,巡音ルカ 浴衣スタイル,200,2013-07-31,2029-01-01,15 +247,0,森ã®å¦–精姫,250,2013-09-11,2029-01-01,16 +248,0,クイン・ビー,250,2013-04-26,2029-01-01,17 +249,0,放課後モード,250,2013-01-30,2029-01-01,18 +250,0,レクイエム,250,2013-12-17,2029-01-01,9 +251,0,ギルティ,250,2014-04-17,2029-01-01,10 +252,0,ジェãƒãƒ©ãƒ«,200,2013-09-25,2029-01-01,11 +253,0,KAITO ãƒãƒ¼ãƒ•ã‚¹ãƒ‘ッツ,1000,2013-06-06,2029-01-01,36 +254,0,KAITO 浴衣スタイル,200,2013-07-31,2029-01-01,13 +255,0,ジーニアス,250,2013-05-15,2029-01-01,14 +256,0,学ラン★パーカー,250,2013-02-15,2029-01-01,15 +257,0,ジェãƒãƒ©ãƒ« AS,200,2013-09-25,2029-01-01,12 +258,0,ブルークリスタル,250,2014-11-05,2029-01-01,10 +259,0,ノエル・ルージュ,250,2013-12-10,2029-01-01,11 +260,0,MEIKO ロングパレオ,1000,2013-07-04,2029-01-01,27 +261,0,MEIKO ウォーターãƒãƒ­,1000,2013-06-06,2029-01-01,28 +262,0,MEIKO 浴衣スタイル,200,2013-07-31,2029-01-01,12 +263,0,ホイッスル,250,2013-05-15,2029-01-01,13 +264,0,グラデュエート,250,2013-02-15,2029-01-01,14 +265,0,BBオペレーター,250,2013-03-12,2029-01-01,15 +266,0,é¡éŸ³ãƒªãƒ³ 浴衣スタイル AS,150,2013-07-31,2029-01-01,18 +267,0,カイト V3,300,2013-03-12,2029-01-01,42 +268,0,深海少女,250,2013-07-31,2029-01-01,169 +269,0,ãƒãƒ‹ãƒ¼ã‚¦ã‚£ãƒƒãƒ—,250,2014-10-02,2029-01-01,148 +270,0,壱ノ桜・桜花,150,2015-03-26,2029-01-01,149 +271,0,リンã¡ã‚ƒã‚“æ„›ã—隊1å·,200,2014-12-18,2029-01-01,151 +272,0,シザーズ,250,2014-06-25,2029-01-01,46 +273,0,å¼ãƒŽæ¡œãƒ»èƒ¡è¶,200,2015-03-26,2029-01-01,47 +274,0,é¡éŸ³ãƒªãƒ³ Future Style,300,2014-12-18,2029-01-01,49 +275,0,トリッカー,250,2014-12-18,2029-01-01,38 +276,0,å¼ãƒŽæ¡œãƒ»æ‰‡èˆž,200,2015-03-26,2029-01-01,39 +277,0,å‚ノ桜・楓香,200,2015-03-26,2029-01-01,38 +278,0,リンã¡ã‚ƒã‚“æ„›ã—隊2å·,200,2014-12-18,2029-01-01,39 +279,0,零ノ桜・蒼雪,150,2015-03-26,2029-01-01,39 +280,0,零ノ桜・紅椿,200,2015-03-26,2029-01-01,31 +281,0,リンケージ,250,2013-12-17,2029-01-01,147 +282,0,スターヴォイス AS,200,2013-09-25,2029-01-01,88 +283,0,é‡éŸ³ãƒ†ãƒˆ,300,2013-09-25,2029-01-01,14 +284,0,雪ミク 2014,300,2013-12-17,2029-01-01,159 +285,0,雪ミク 2014 AS,100,2029-01-01,2029-01-01,160 +286,0,CA åˆéŸ³ãƒŸã‚¯,250,2014-01-15,2029-01-01,122 +287,0,CA é¡éŸ³ãƒªãƒ³,250,2014-01-15,2029-01-01,30 +288,0,CA 巡音ルカ,250,2014-01-15,2029-01-01,26 +289,0,CA メイコ,250,2014-01-15,2029-01-01,19 +290,0,マジカルミライ,300,2014-02-12,2029-01-01,170 +291,0,Cheerful ミク,150,2014-02-25,2029-01-01,123 +292,0,Cheerful ミク AS,150,2014-02-25,2029-01-01,124 +293,0,Cheerful リン,200,2014-02-25,2029-01-01,31 +294,0,Cheerful レン,200,2014-02-25,2029-01-01,26 +295,0,Cheerful ルカ,200,2014-02-25,2029-01-01,27 +296,0,Cheerful カイト,200,2014-02-25,2029-01-01,23 +297,0,Cheerful メイコ,200,2014-02-25,2029-01-01,20 +298,0,Cheerful カイト AS,200,2029-01-01,2029-01-01,24 +299,0,åˆéŸ³ãƒŸã‚¯ V3,300,2014-08-28,2029-01-01,171 +300,0,ã‚¢ãƒãƒ³ã‚¬ãƒ¼ãƒ‰,250,2015-07-30,2029-01-01,97 +301,0,ナナイロライン,250,2015-08-28,2029-01-01,98 +302,0,ブレス・ユー,250,2015-10-08,2029-01-01,99 +303,0,花詞,250,2015-11-05,2029-01-01,100 +304,0,è¯è»Š,250,2015-11-05,2029-01-01,101 +305,0,リグレット,250,2015-11-05,2029-01-01,102 +306,0,マリオãƒãƒƒãƒˆ,250,2015-11-05,2029-01-01,104 +308,0,ライアー,250,2016-01-28,2029-01-01,105 +309,0,月光アゲãƒ,250,2015-12-22,2029-01-01,106 +310,0,サイレン,250,2015-10-08,2029-01-01,107 +311,0,ローザ・ビアンカ,250,2016-06-30,2029-01-01,108 +313,0,メテオライト,250,2015-07-30,2029-01-01,111 +314,0,シャノワール,200,2015-06-26,2029-01-01,112 +315,0,シャノワール AS,200,2015-06-26,2029-01-01,113 +316,0,シュープリーム,250,2016-04-01,2029-01-01,114 +317,0,オレンジブロッサム,250,2015-08-28,2029-01-01,115 +318,0,ディメンション,250,2015-07-30,2029-01-01,116 +319,0,ストリートãƒãƒƒãƒ—,250,2016-01-28,2029-01-01,117 +320,0,ゆるãµã‚パステル,250,2016-01-28,2029-01-01,118 +321,0,夢見るパンダ,250,2015-06-26,2029-01-01,22 +322,0,フェイカー,250,2016-01-28,2029-01-01,23 +323,0,ヒマワリ,200,2015-12-22,2029-01-01,24 +324,0,ソレイユ,250,2015-12-22,2029-01-01,26 +325,0,フェアリーワンピース,250,2015-12-22,2029-01-01,27 +326,0,æ‹ã™ã‚‹ã‚·ãƒ­ã‚¯ãƒž,250,2015-06-26,2029-01-01,19 +327,0,アヤサキ,200,2015-12-22,2029-01-01,20 +328,0,シエル,250,2015-12-22,2029-01-01,22 +329,0,イレイザー,250,2016-01-28,2029-01-01,23 +330,0,ホワイトエッジ,250,2015-12-22,2029-01-01,24 +331,0,サクセサー,250,2015-07-30,2029-01-01,19 +332,0,テンプテーション,250,2016-01-28,2029-01-01,20 +334,0,リクルーター,250,2015-01-27,2029-01-01,22 +335,0,フローラル,250,2016-01-28,2029-01-01,23 +336,0,ローザ・ブルー,250,2016-06-30,2029-01-01,16 +338,0,オリジãƒã‚¤ã‚¿ãƒ¼,250,2016-01-28,2029-01-01,19 +339,0,ホリデイ,250,2015-11-05,2029-01-01,20 +340,0,ブレイジング,250,2015-11-05,2029-01-01,16 +341,0,マリーン・リボン,250,2015-11-05,2029-01-01,17 +343,0,ã†ã•ã¿ã¿ãƒ‘ーカー,250,2016-08-31,2029-01-01,119 +344,0,アルパーカー R,250,2016-09-29,2029-01-01,28 +345,0,アルパーカー L,250,2016-09-29,2029-01-01,25 +346,0,ã­ã“ã¿ã¿ãƒ‘ーカー,250,2016-08-31,2029-01-01,24 +347,0,ãŠã‚µã‚«ãƒŠã¤ãªãŽ,250,2016-10-27,2029-01-01,21 +348,0,ã²ã¤ã˜ã•ã‚“ウェア,250,2016-10-27,2029-01-01,18 +349,0,åˆéŸ³ãƒŸã‚¯ スイムウェア/ST,1000,2016-07-28,2029-01-01,142 +350,0,é¡éŸ³ãƒªãƒ³ スイムウェア/ST,1000,2016-07-28,2029-01-01,40 +351,0,é¡éŸ³ãƒ¬ãƒ³ スイムウェア/ST,300,2016-07-28,2029-01-01,34 +352,0,巡音ルカ スイムウェアP/ST,1000,2016-07-28,2029-01-01,33 +353,0,KAITO スイムウェアV/ST,750,2016-07-28,2029-01-01,33 +354,0,KAITO スイムウェアV AS/ST,750,2016-07-28,2029-01-01,35 +355,0,MEIKO スイムウェア/ST,1000,2016-07-28,2029-01-01,25 +356,0,M・S・J,300,2016-04-01,2029-01-01,15 +359,0,out of the gravity,250,2014-08-28,2029-01-01,126 +360,0,インタビュア ミク,250,2014-10-02,2029-01-01,127 +361,0,インタビュア ルカ,250,2014-10-02,2029-01-01,28 +362,0,スイートパンプキン,300,2014-10-02,2029-01-01,172 +363,0,MEIKO V3,300,2014-11-05,2029-01-01,34 +364,0,雪ミク 2015,300,2014-12-18,2029-01-01,161 +365,0,壱ノ桜・白桜花,150,2015-03-26,2029-01-01,150 +366,0,零ノ桜・白雪,150,2015-03-26,2029-01-01,40 +367,0,ダイヤモンドダスト,250,2015-01-27,2029-01-01,25 +368,0,アイスフォグ,250,2015-01-27,2029-01-01,27 +369,0,テレカクシパーカー 黄色,250,2015-04-23,2029-01-01,28 +370,0,テレカクシパーカー é’色,250,2015-04-23,2029-01-01,26 +371,0,スãƒãƒ£ãƒ©ã‚«ãƒãƒ„ãƒ,250,2015-05-28,2029-01-01,130 +372,0,マジックシェフ,250,2015-06-26,2029-01-01,32 +373,0,グラデーションリゾート,1000,2015-08-06,2029-01-01,146 +374,0,ミラクルスターリゾート,1000,2015-08-06,2029-01-01,44 +375,0,ãƒãƒƒãƒ—スターリゾート,1000,2015-08-06,2029-01-01,37 +376,0,トゥインクルリゾート,1000,2015-08-06,2029-01-01,36 +377,0,プレイドリゾート,1000,2015-08-06,2029-01-01,37 +378,0,ãƒã‚¤ã‚«ãƒ©ãƒ¼ãƒªãƒœãƒ³ãƒªã‚¾ãƒ¼ãƒˆ,1000,2015-08-06,2029-01-01,29 +379,0,ã‚ã‚Šãµã‚ŒãƒŸã‚¯,250,2015-08-28,2029-01-01,128 +380,0,P4Dスタイル,300,2016-06-23,2029-01-01,173 +381,0,PIANO*GIRL,250,2015-10-08,2029-01-01,129 +382,0,é‡éŸ³ãƒ†ãƒˆ スイムウェア,1000,2015-11-05,2029-01-01,16 +383,0,巡音ルカV4X,300,2016-01-28,2029-01-01,44 +385,0,Trip The Light Fantastic,250,2016-04-27,2029-01-01,132 +386,0,Poppin Delight,250,2016-04-27,2029-01-01,33 +387,0,Bebop Knave,250,2016-04-27,2029-01-01,29 +389,0,プランセス・ブランシュ,250,2016-04-27,2029-01-01,133 +390,0,プランス・ブラン,250,2016-04-27,2029-01-01,27 +391,0,アドレサンスプリンセス,250,2016-04-27,2029-01-01,343 +392,0,アドレサンスナイト,250,2016-04-27,2029-01-01,30 +393,0,マーãƒãƒ»ãƒ˜ã‚¤ãƒ¤,250,2016-08-31,2029-01-01,136 +394,0,アゲアゲアゲイン,250,2016-02-26,2029-01-01,131 +395,0,エトワール,250,2016-06-30,2029-01-01,135 +398,0,フェアウェル,250,2016-06-30,2029-01-01,134 +399,0,天袖,250,2016-12-21,2029-01-01,36 +402,0,雪ミク 2016,300,2015-12-22,2029-01-01,162 +403,0,パジャマパーティ ミク,250,2017-01-26,2029-01-01,139 +404,0,パジャマパーティ リン,250,2017-01-26,2029-01-01,38 +405,0,パジャマパーティ レン,250,2017-01-26,2029-01-01,32 +406,0,パジャマパーティ ルカ,250,2017-01-26,2029-01-01,30 +407,0,パジャマパーティ カイト,250,2017-01-26,2029-01-01,30 +408,0,パジャマパーティ メイコ,250,2017-01-26,2029-01-01,23 +409,0,Phantom Thief ミク,250,2016-10-27,2029-01-01,137 +410,0,Phantom Thief リン,250,2016-10-27,2029-01-01,35 +411,0,Phantom Thief メイコ,250,2016-10-27,2029-01-01,21 +412,0,Phantom Thief カイト,250,2016-10-27,2029-01-01,28 +413,0,鉄é“員・鶯,250,2016-12-21,2029-01-01,138 +414,0,鉄é“員・金糸雀,250,2016-12-21,2029-01-01,37 +415,0,鉄é“員・銀朱,250,2016-12-21,2029-01-01,31 +416,0,鉄é“員・薔薇,250,2016-12-21,2029-01-01,29 +417,0,鉄é“員・空,250,2016-12-21,2029-01-01,29 +418,0,鉄é“員・紅葡è„,250,2016-12-21,2029-01-01,22 +419,0,雪ミク 2017,300,2016-12-21,2029-01-01,163 +420,0,ヒマワリ AS,200,2015-12-22,2029-01-01,25 +421,0,アヤサキ AS,200,2015-12-22,2029-01-01,21 +422,0,ã¿ãã‚Šã™ãŸã‚‹â˜†,300,2016-05-26,2029-01-01,125 +423,0,マイディアãƒãƒ‹ãƒ¼,1000,2017-08-31,2029-01-01,174 +424,0,ローザ・ノッテ,250,2016-12-21,2029-01-01,110 +425,0,ローザ・ルーノ,250,2016-12-21,2029-01-01,18 +426,0,GHOST,250,2017-12-13,2029-01-01,175 +427,0,セレブレーション,300,2017-12-13,2029-01-01,176 +428,0,雪ミク 2018 AS,300,2019-04-24,2029-01-01,165 +429,0,雪ミク 2019,300,2019-04-24,2029-01-01,166 +430,0,雪ミク 2018,300,2019-04-24,2029-01-01,164 +431,0,Catch The Wave,900,2020-02-20,2029-01-01,177 diff --git a/titles/diva/database.py b/titles/diva/database.py new file mode 100644 index 0000000..f43c42e --- /dev/null +++ b/titles/diva/database.py @@ -0,0 +1,12 @@ +from core.data import Data +from core.config import CoreConfig +from titles.diva.schema import DivaProfileData, DivaScoreData, DivaItemData, DivaStaticData + +class DivaData(Data): + def __init__(self, cfg: CoreConfig) -> None: + super().__init__(cfg) + + self.profile = DivaProfileData(self.config, self.session) + self.score = DivaScoreData(self.config, self.session) + self.item = DivaItemData(self.config, self.session) + self.static = DivaStaticData(self.config, self.session) diff --git a/titles/diva/index.py b/titles/diva/index.py new file mode 100644 index 0000000..d48a125 --- /dev/null +++ b/titles/diva/index.py @@ -0,0 +1,105 @@ +from twisted.web.http import Request +import yaml +import logging, coloredlogs +from logging.handlers import TimedRotatingFileHandler +import zlib +import json +import urllib.parse +import base64 + +from core.config import CoreConfig +from titles.diva.config import DivaConfig +from titles.diva.base import DivaBase + +class DivaServlet(): + def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None: + self.core_cfg = core_cfg + self.game_cfg = DivaConfig() + self.game_cfg.update(yaml.safe_load(open(f"{cfg_dir}/diva.yaml"))) + + self.base = DivaBase(core_cfg, self.game_cfg) + + self.logger = logging.getLogger("diva") + log_fmt_str = "[%(asctime)s] Diva | %(levelname)s | %(message)s" + log_fmt = logging.Formatter(log_fmt_str) + fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.core_cfg.server.log_dir, "diva"), encoding='utf8', + when="d", backupCount=10) + + fileHandler.setFormatter(log_fmt) + + consoleHandler = logging.StreamHandler() + consoleHandler.setFormatter(log_fmt) + + self.logger.addHandler(fileHandler) + self.logger.addHandler(consoleHandler) + + self.logger.setLevel(self.game_cfg.server.loglevel) + coloredlogs.install(level=self.game_cfg.server.loglevel, logger=self.logger, fmt=log_fmt_str) + + def render_POST(self, req: Request, version: int, url_path: str) -> bytes: + req_raw = req.content.getvalue() + url_header = req.getAllHeaders() + + #Ping Dispatch + if "THIS_STRING_SEPARATES"in str(url_header): + binary_request = req_raw.splitlines() + binary_cmd_decoded = binary_request[3].decode("utf-8") + binary_array = binary_cmd_decoded.split('&') + + bin_req_data = {} + + for kvp in binary_array: + split_bin = kvp.split("=") + bin_req_data[split_bin[0]] = split_bin[1] + + self.logger.info(f"Binary {bin_req_data['cmd']} Request") + self.logger.debug(bin_req_data) + + handler = getattr(self.base, f"handle_{bin_req_data['cmd']}_request") + resp = handler(bin_req_data) + + self.logger.debug(f"Response cmd={bin_req_data['cmd']}&req_id={bin_req_data['req_id']}&stat=ok{resp}") + return f"cmd={bin_req_data['cmd']}&req_id={bin_req_data['req_id']}&stat=ok{resp}".encode('utf-8') + + #Main Dispatch + json_string = json.dumps(req_raw.decode("utf-8")) #Take the response and decode as UTF-8 and dump + b64string = json_string.replace(r'\n', '\n') # Remove all \n and separate them as new lines + gz_string = base64.b64decode(b64string) # Decompressing the base64 string + + try: + url_data = zlib.decompress( gz_string ).decode("utf-8") # Decompressing the gzip + except zlib.error as e: + self.logger.error(f"Failed to defalte! {e} -> {gz_string}") + return "stat=0" + + req_kvp = urllib.parse.unquote(url_data) + req_data = {} + + # We then need to split each parts with & so we can reuse them to fill out the requests + splitted_request = str.split(req_kvp, "&") + for kvp in splitted_request: + split = kvp.split("=") + req_data[split[0]] = split[1] + + self.logger.info(f"{req_data['cmd']} Request") + self.logger.debug(req_data) + + func_to_find = f"handle_{req_data['cmd']}_request" + + # Load the requests + try: + handler = getattr(self.base, func_to_find) + resp = handler(req_data) + + except AttributeError as e: + self.logger.warning(f"Unhandled {req_data['cmd']} request {e}") + return f"cmd={req_data['cmd']}&req_id={req_data['req_id']}&stat=ok".encode('utf-8') + + except Exception as e: + self.logger.error(f"Error handling method {func_to_find} {e}") + return f"cmd={req_data['cmd']}&req_id={req_data['req_id']}&stat=ok".encode('utf-8') + + req.responseHeaders.addRawHeader(b"content-type", b"text/plain") + self.logger.debug(f"Response cmd={req_data['cmd']}&req_id={req_data['req_id']}&stat=ok{resp}") + + return f"cmd={req_data['cmd']}&req_id={req_data['req_id']}&stat=ok{resp}".encode('utf-8') diff --git a/titles/diva/read.py b/titles/diva/read.py new file mode 100644 index 0000000..c597315 --- /dev/null +++ b/titles/diva/read.py @@ -0,0 +1,203 @@ +from typing import Optional, Dict, List +from os import walk, path +import urllib + +from read import BaseReader +from core.config import CoreConfig +from titles.diva.database import DivaData +from titles.diva.const import DivaConstants + +class DivaReader(BaseReader): + def __init__(self, config: CoreConfig, version: int, bin_dir: Optional[str], opt_dir: Optional[str], extra: Optional[str]) -> None: + super().__init__(config, version, bin_dir, opt_dir, extra) + self.data = DivaData(config) + + try: + self.logger.info(f"Start importer for {DivaConstants.game_ver_to_string(version)}") + except IndexError: + self.logger.error(f"Invalid project diva version {version}") + exit(1) + + def read(self) -> None: + pull_bin_ram = True + pull_bin_rom = True + pull_opt_rom = True + + if not path.exists(f"{self.bin_dir}/ram"): + self.logger.warn(f"Couldn't find ram folder in {self.bin_dir}, skipping") + pull_bin_ram = False + + if not path.exists(f"{self.bin_dir}/rom"): + self.logger.warn(f"Couldn't find rom folder in {self.bin_dir}, skipping") + pull_bin_rom = False + + if self.opt_dir is not None: + opt_dirs = self.get_data_directories(self.opt_dir) + else: + pull_opt_rom = False + self.logger.warn("No option directory specified, skipping") + + if pull_bin_ram: + self.read_ram(f"{self.bin_dir}/ram") + if pull_bin_rom: + self.read_rom(f"{self.bin_dir}/rom") + if pull_opt_rom: + for dir in opt_dirs: + self.read_rom(f"{dir}/rom") + + def read_ram(self, ram_root_dir: str) -> None: + self.logger.info(f"Read RAM from {ram_root_dir}") + + if path.exists(f"{ram_root_dir}/databank"): + for root, dirs, files in walk(f"{ram_root_dir}/databank"): + for file in files: + if file.startswith("ShopCatalog_") or file.startswith("CustomizeItemCatalog_") or \ + (file.startswith("QuestInfo") and not file.startswith("QuestInfoTm")): + + with open(f"{root}/{file}", "r") as f: + file_data: str = urllib.parse.unquote(urllib.parse.unquote(f.read())) + if file_data == "***": + self.logger.info(f"{file} is empty, skipping") + continue + + file_lines: List[str] = file_data.split("\n") + + for line in file_lines: + split = line.split(",") + + if not split[0]: + split.pop(0) + + if file.startswith("ShopCatalog_"): + for x in range(0, len(split), 7): + self.logger.info(f"Added shop item {split[x+0]}") + + self.data.static.put_shop(self.version, split[x+0], split[x+2], split[x+6], split[x+3], + split[x+1], split[x+4], split[x+5]) + + elif file.startswith("CustomizeItemCatalog_") and len(split) >= 7: + for x in range(0, len(split), 7): + self.logger.info(f"Added item {split[x+0]}") + + self.data.static.put_items(self.version, split[x+0], split[x+2], split[x+6], split[x+3], + split[x+1], split[x+4], split[x+5]) + + elif file.startswith("QuestInfo") and len(split) >= 9: + self.logger.info(f"Added quest {split[0]}") + + self.data.static.put_quests(self.version, split[0], split[6], split[2], split[3], + split[7], split[8], split[1], split[4], split[5]) + + else: + continue + else: + self.logger.warn(f"Databank folder not found in {ram_root_dir}, skipping") + + def read_rom(self, rom_root_dir: str) -> None: + self.logger.info(f"Read ROM from {rom_root_dir}") + pv_list: Dict[str, Dict] = {} + + if path.exists(f"{rom_root_dir}/mdata_pv_db.txt"): + file_path = f"{rom_root_dir}/mdata_pv_db.txt" + elif path.exists(f"{rom_root_dir}/pv_db.txt"): + file_path = f"{rom_root_dir}/pv_db.txt" + else: + self.logger.warn(f"Cannot find pv_db.txt or mdata_pv_db.txt in {rom_root_dir}, skipping") + return + + with open(file_path, "r", encoding="utf-8") as f: + + for line in f.readlines(): + + if line.startswith("#") or not line: + continue + + line_split = line.split("=") + if len(line_split) != 2: + continue + + key = line_split[0] + val = line_split[1] + if val.endswith("\n"): + val = val[:-1] + + key_split = key.split(".") + pv_id = key_split[0] + key_args = [] + + for x in range(1, len(key_split)): + key_args.append(key_split[x]) + + try: + pv_list[pv_id] = self.add_branch(pv_list[pv_id], key_args, val) + except KeyError: + pv_list[pv_id] = {} + pv_list[pv_id] = self.add_branch(pv_list[pv_id], key_args, val) + + + for pv_id, pv_data in pv_list.items(): + song_id = int(pv_id.split("_")[1]) + if "songinfo" not in pv_data: + continue + if "illustrator" not in pv_data["songinfo"]: + pv_data["songinfo"]["illustrator"] = "-" + if "arranger" not in pv_data["songinfo"]: + pv_data["songinfo"]["arranger"] = "-" + if "lyrics" not in pv_data["songinfo"]: + pv_data["songinfo"]["lyrics"] = "-" + if "music" not in pv_data["songinfo"]: + pv_data["songinfo"]["music"] = "-" + + if "easy" in pv_data['difficulty'] and '0' in pv_data['difficulty']['easy']: + diff = pv_data['difficulty']['easy']['0']['level'].split('_') + self.logger.info(f"Added song {song_id} chart 0") + + self.data.static.put_music(self.version, song_id, 0, pv_data["song_name"], pv_data["songinfo"]["arranger"], + pv_data["songinfo"]["illustrator"], pv_data["songinfo"]["lyrics"], pv_data["songinfo"]["music"], + float(f"{diff[2]}.{diff[3]}"), pv_data["bpm"], pv_data["date"]) + + if "normal" in pv_data['difficulty'] and '0' in pv_data['difficulty']['normal']: + diff = pv_data['difficulty']['normal']['0']['level'].split('_') + self.logger.info(f"Added song {song_id} chart 1") + + self.data.static.put_music(self.version, song_id, 1, pv_data["song_name"], pv_data["songinfo"]["arranger"], + pv_data["songinfo"]["illustrator"], pv_data["songinfo"]["lyrics"], pv_data["songinfo"]["music"], + float(f"{diff[2]}.{diff[3]}"), pv_data["bpm"], pv_data["date"]) + + if "hard" in pv_data['difficulty'] and '0' in pv_data['difficulty']['hard']: + diff = pv_data['difficulty']['hard']['0']['level'].split('_') + self.logger.info(f"Added song {song_id} chart 2") + + self.data.static.put_music(self.version, song_id, 2, pv_data["song_name"], pv_data["songinfo"]["arranger"], + pv_data["songinfo"]["illustrator"], pv_data["songinfo"]["lyrics"], pv_data["songinfo"]["music"], + float(f"{diff[2]}.{diff[3]}"), pv_data["bpm"], pv_data["date"]) + + if "extreme" in pv_data['difficulty']: + if "0" in pv_data['difficulty']['extreme']: + diff = pv_data['difficulty']['extreme']['0']['level'].split('_') + self.logger.info(f"Added song {song_id} chart 3") + + self.data.static.put_music(self.version, song_id, 3, pv_data["song_name"], pv_data["songinfo"]["arranger"], + pv_data["songinfo"]["illustrator"], pv_data["songinfo"]["lyrics"], pv_data["songinfo"]["music"], + float(f"{diff[2]}.{diff[3]}"), pv_data["bpm"], pv_data["date"]) + + if "1" in pv_data['difficulty']['extreme']: + diff = pv_data['difficulty']['extreme']['1']['level'].split('_') + self.logger.info(f"Added song {song_id} chart 4") + + self.data.static.put_music(self.version, song_id, 4, pv_data["song_name"], pv_data["songinfo"]["arranger"], + pv_data["songinfo"]["illustrator"], pv_data["songinfo"]["lyrics"], pv_data["songinfo"]["music"], + float(f"{diff[2]}.{diff[3]}"), pv_data["bpm"], pv_data["date"]) + + def add_branch(self, tree: Dict, vector: List, value: str): + """ + Recursivly adds nodes to a dictionary + Author: iJames on StackOverflow + """ + key = vector[0] + tree[key] = value \ + if len(vector) == 1 \ + else self.add_branch(tree[key] if key in tree else {}, + vector[1:], + value) + return tree \ No newline at end of file diff --git a/titles/diva/schema/__init__.py b/titles/diva/schema/__init__.py new file mode 100644 index 0000000..57431ef --- /dev/null +++ b/titles/diva/schema/__init__.py @@ -0,0 +1,6 @@ +from titles.diva.schema.profile import DivaProfileData +from titles.diva.schema.score import DivaScoreData +from titles.diva.schema.item import DivaItemData +from titles.diva.schema.static import DivaStaticData + +__all__ = [DivaProfileData, DivaScoreData, DivaItemData, DivaStaticData] diff --git a/titles/diva/schema/item.py b/titles/diva/schema/item.py new file mode 100644 index 0000000..96d8ca5 --- /dev/null +++ b/titles/diva/schema/item.py @@ -0,0 +1,50 @@ +from typing import Optional, Dict, List +from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_, case +from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean +from sqlalchemy.schema import ForeignKey +from sqlalchemy.sql import func +from sqlalchemy.dialects.mysql import insert + +from core.data.schema import BaseData, metadata + +shop = Table( + "diva_profile_shop", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("version", Integer, nullable=False), + Column("mdl_eqp_ary", String(32)), + UniqueConstraint("user", "version", name="diva_profile_shop_uk"), + mysql_charset='utf8mb4' +) + +class DivaItemData(BaseData): + def put_shop(self, aime_id: int, version: int, mdl_eqp_ary: str) -> None: + sql = insert(shop).values( + version=version, + user=aime_id, + mdl_eqp_ary=mdl_eqp_ary + ) + + conflict = sql.on_duplicate_key_update( + mdl_eqp_ary = sql.inserted.mdl_eqp_ary + ) + + result = self.execute(conflict) + if result is None: + self.logger.error(f"{__name__} Failed to insert diva profile! aime id: {aime_id} array: {mdl_eqp_ary}") + return None + return result.lastrowid + + def get_shop(self, aime_id: int, version: int) -> Optional[List[Dict]]: + """ + Given a game version and either a profile or aime id, return the profile + """ + sql = shop.select(and_( + shop.c.version == version, + shop.c.user == aime_id + )) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() \ No newline at end of file diff --git a/titles/diva/schema/profile.py b/titles/diva/schema/profile.py new file mode 100644 index 0000000..72d68ed --- /dev/null +++ b/titles/diva/schema/profile.py @@ -0,0 +1,113 @@ +from typing import Optional, Dict, List +from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_ +from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON +from sqlalchemy.schema import ForeignKey +from sqlalchemy.sql import func, select +from sqlalchemy.dialects.mysql import insert + +from core.data.schema import BaseData, metadata + +profile = Table( + "diva_profile", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("version", Integer, nullable=False), + Column("player_name", String(8), nullable=False), + Column("lv_str", String(24), nullable=False, server_default="Dab on 'em"), + Column("lv_num", Integer, nullable=False, server_default="0"), + Column("lv_pnt", Integer, nullable=False, server_default="0"), + Column("vcld_pts", Integer, nullable=False, server_default="0"), + Column("hp_vol", Integer, nullable=False, server_default="100"), + Column("btn_se_vol", Integer, nullable=False, server_default="100"), + Column("btn_se_vol2", Integer, nullable=False, server_default="100"), + Column("sldr_se_vol2", Integer, nullable=False, server_default="100"), + Column("sort_kind", Integer, nullable=False, server_default="2"), + Column("use_pv_mdl_eqp", String(8), nullable=False, server_default="true"), + Column("use_pv_btn_se_eqp", String(8), nullable=False, server_default="true"), + Column("use_pv_sld_se_eqp", String(8), nullable=False, server_default="false"), + Column("use_pv_chn_sld_se_eqp", String(8), nullable=False, server_default="false"), + Column("use_pv_sldr_tch_se_eqp", String(8), nullable=False, server_default="false"), + Column("nxt_pv_id", Integer, nullable=False, server_default="708"), + Column("nxt_dffclty", Integer, nullable=False, server_default="2"), + Column("nxt_edtn", Integer, nullable=False, server_default="0"), + Column("dsp_clr_brdr", Integer, nullable=False, server_default="7"), + Column("dsp_intrm_rnk", Integer, nullable=False, server_default="1"), + Column("dsp_clr_sts", Integer, nullable=False, server_default="1"), + Column("rgo_sts", Integer, nullable=False, server_default="1"), + Column("lv_efct_id", Integer, nullable=False, server_default="0"), + Column("lv_plt_id", Integer, nullable=False, server_default="1"), + Column("my_qst_id", String(128), server_default="-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1"), + Column("my_qst_sts", String(128), server_default="-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1"), + UniqueConstraint("user", "version", name="diva_profile_uk"), + mysql_charset='utf8mb4' +) + +class DivaProfileData(BaseData): + def create_profile(self, version: int, aime_id: int, player_name: str) -> Optional[int]: + """ + Given a game version, aime id, and player_name, create a profile and return it's ID + """ + sql = insert(profile).values( + version=version, + user=aime_id, + player_name=player_name + ) + + conflict = sql.on_duplicate_key_update( + player_name = sql.inserted.player_name + ) + + result = self.execute(conflict) + if result is None: + self.logger.error(f"{__name__} Failed to insert diva profile! aime id: {aime_id} username: {player_name}") + return None + return result.lastrowid + + def update_profile(self, profile_id: int, lv_num: int, lv_pnt: int, vcld_pts: int, hp_vol: int, btn_se_vol: int, btn_se_vol2: int, sldr_se_vol2: int, sort_kind: int, use_pv_mdl_eqp: str, use_pv_btn_se_eqp: str, use_pv_sld_se_eqp: str, use_pv_chn_sld_se_eqp: str, use_pv_sldr_tch_se_eqp: str, nxt_pv_id: int, nxt_dffclty: int, nxt_edtn: int, dsp_clr_brdr: int, dsp_intrm_rnk: int, dsp_clr_sts: int, rgo_sts: int, lv_efct_id: int, lv_plt_id: int, my_qst_id: str, my_qst_sts: str) -> None: + sql = profile.update(profile.c.user == profile_id).values( + + lv_num = lv_num, + lv_pnt = lv_pnt, + vcld_pts = vcld_pts, + hp_vol = hp_vol, + btn_se_vol = btn_se_vol, + btn_se_vol2 = btn_se_vol2, + sldr_se_vol2 = sldr_se_vol2, + sort_kind = sort_kind, + use_pv_mdl_eqp = use_pv_mdl_eqp, + use_pv_btn_se_eqp = use_pv_btn_se_eqp, + use_pv_sld_se_eqp = use_pv_sld_se_eqp, + use_pv_chn_sld_se_eqp = use_pv_chn_sld_se_eqp, + use_pv_sldr_tch_se_eqp = use_pv_sldr_tch_se_eqp, + nxt_pv_id = nxt_pv_id, + nxt_dffclty = nxt_dffclty, + nxt_edtn = nxt_edtn, + dsp_clr_brdr = dsp_clr_brdr, + dsp_intrm_rnk = dsp_intrm_rnk, + dsp_clr_sts = dsp_clr_sts, + rgo_sts = rgo_sts, + lv_efct_id = lv_efct_id, + lv_plt_id = lv_plt_id, + my_qst_id = my_qst_id, + my_qst_sts = my_qst_sts + + ) + + result = self.execute(sql) + if result is None: + self.logger.error(f"update_profile: failed to update profile! profile: {profile_id}") + return None + + def get_profile(self, aime_id: int, version: int) -> Optional[List[Dict]]: + """ + Given a game version and either a profile or aime id, return the profile + """ + sql = profile.select(and_( + profile.c.version == version, + profile.c.user == aime_id + )) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() diff --git a/titles/diva/schema/score.py b/titles/diva/schema/score.py new file mode 100644 index 0000000..6cd0346 --- /dev/null +++ b/titles/diva/schema/score.py @@ -0,0 +1,141 @@ +from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_ +from sqlalchemy.types import Integer, String, TIMESTAMP, JSON, Boolean +from sqlalchemy.schema import ForeignKey +from sqlalchemy.sql import func +from sqlalchemy.dialects.mysql import insert +from typing import Optional, List, Dict, Any + +from core.data.schema import BaseData, metadata +from core.data import cached + +score = Table( + "diva_score", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade"), nullable=False), + Column("version", Integer), + Column("pv_id", Integer), + Column("difficulty", Integer), + Column("score", Integer), + Column("atn_pnt", Integer), + Column("clr_kind", Integer), + Column("sort_kind", Integer), + Column("cool", Integer), + Column("fine", Integer), + Column("safe", Integer), + Column("sad", Integer), + Column("worst", Integer), + Column("max_combo", Integer), + UniqueConstraint("user", "pv_id", "difficulty", name="diva_score_uk"), + mysql_charset='utf8mb4' +) + +playlog = Table( + "diva_playlog", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade"), nullable=False), + Column("version", Integer), + Column("pv_id", Integer), + Column("difficulty", Integer), + Column("score", Integer), + Column("atn_pnt", Integer), + Column("clr_kind", Integer), + Column("sort_kind", Integer), + Column("cool", Integer), + Column("fine", Integer), + Column("safe", Integer), + Column("sad", Integer), + Column("worst", Integer), + Column("max_combo", Integer), + Column("date_scored", TIMESTAMP, server_default=func.now()), + mysql_charset='utf8mb4' +) + +class DivaScoreData(BaseData): + def put_best_score(self, user_id: int, game_version: int, song_id: int, difficulty: int, song_score: int, atn_pnt: int, + clr_kind: int, sort_kind:int, cool: int, fine: int, safe: int, sad: int, worst: int, max_combo: int) -> Optional[int]: + """ + Update the user's best score for a chart + """ + sql = insert(score).values( + user=user_id, + version=game_version, + pv_id = song_id, + difficulty=difficulty, + score=song_score, + atn_pnt = atn_pnt, + clr_kind = clr_kind, + sort_kind = sort_kind, + cool = cool, + fine = fine, + safe = safe, + sad = sad, + worst = worst, + max_combo = max_combo, + ) + + conflict = sql.on_duplicate_key_update( + score=song_score, + atn_pnt = atn_pnt, + clr_kind = clr_kind, + sort_kind = sort_kind, + cool = cool, + fine = fine, + safe = safe, + sad = sad, + worst = worst, + max_combo = max_combo, + ) + + result = self.execute(conflict) + if result is None: + self.logger.error(f"{__name__} failed to insert best score! profile: {user_id}, song: {song_id}") + return None + + return result.lastrowid + + def put_playlog(self, user_id: int, game_version: int, song_id: int, difficulty: int, song_score: int, atn_pnt: int, + clr_kind: int, sort_kind:int, cool: int, fine: int, safe: int, sad: int, worst: int, max_combo: int) -> Optional[int]: + """ + Add an entry to the user's play log + """ + sql = playlog.insert().values( + user=user_id, + version=game_version, + pv_id = song_id, + difficulty=difficulty, + score=song_score, + atn_pnt = atn_pnt, + clr_kind = clr_kind, + sort_kind = sort_kind, + cool = cool, + fine = fine, + safe = safe, + sad = sad, + worst = worst, + max_combo = max_combo + ) + + result = self.execute(sql) + if result is None: + self.logger.error(f"{__name__} failed to insert playlog! profile: {user_id}, song: {song_id}, chart: {difficulty}") + return None + + return result.lastrowid + + def get_best_score(self, user_id: int, pv_id: int, chart_id: int) -> Optional[Dict]: + sql = score.select( + and_(score.c.user == user_id, score.c.pv_id == pv_id, score.c.difficulty == chart_id) + ) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() + + def get_best_scores(self, user_id: int) -> Optional[Dict]: + sql = score.select(score.c.user == user_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() diff --git a/titles/diva/schema/static.py b/titles/diva/schema/static.py new file mode 100644 index 0000000..6544420 --- /dev/null +++ b/titles/diva/schema/static.py @@ -0,0 +1,222 @@ +from typing import Dict, List, Optional +from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_ +from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, Float +from sqlalchemy.engine.base import Connection +from sqlalchemy.engine import Row +from sqlalchemy.schema import ForeignKey +from sqlalchemy.sql import func, select +from sqlalchemy.dialects.mysql import insert + +from core.data.schema import BaseData, metadata + +music = Table( + "diva_static_music", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("version", Integer, nullable=False), + Column("songId", Integer), + Column("chartId", Integer), + Column("title", String(255)), + Column("vocaloid_arranger", String(255)), + Column("pv_illustrator", String(255)), + Column("lyrics", String(255)), + Column("bg_music", String(255)), + Column("level", Float), + Column("bpm", Integer), + Column("date", String(255)), + UniqueConstraint("version", "songId", "chartId", name="diva_static_music_uk"), + mysql_charset='utf8mb4' +) + +quests = Table( + "diva_static_quests", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("version", Integer, nullable=False), + Column("questId", Integer), + Column("name", String(255)), + Column("quest_enable", Boolean, server_default="1"), + Column("kind", Integer), + Column("unknown_0", Integer), + Column("unknown_1", Integer), + Column("unknown_2", Integer), + Column("quest_order", Integer), + Column("start_datetime", String(255)), + Column("end_datetime", String(255)), + + UniqueConstraint("version", "questId", name="diva_static_quests_uk"), + mysql_charset='utf8mb4' +) + +shop = Table( + "diva_static_shop", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("version", Integer, nullable=False), + Column("shopId", Integer), + Column("name", String(255)), + Column("type", Integer), + Column("points", Integer), + Column("unknown_0", Integer), + Column("start_date", String(255)), + Column("end_date", String(255)), + Column("enabled", Boolean, server_default="1"), + UniqueConstraint("version", "shopId", name="diva_static_shop_uk"), + mysql_charset='utf8mb4' +) + +items = Table( + "diva_static_items", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("version", Integer, nullable=False), + Column("itemId", Integer), + Column("name", String(255)), + Column("type", Integer), + Column("points", Integer), + Column("unknown_0", Integer), + Column("start_date", String(255)), + Column("end_date", String(255)), + Column("enabled", Boolean, server_default="1"), + UniqueConstraint("version", "itemId", name="diva_static_items_uk"), + mysql_charset='utf8mb4' +) + +class DivaStaticData(BaseData): + def put_quests(self, version: int, questId: int, name: str, kind: int, unknown_0: int, unknown_1: int, unknown_2: int, quest_order: int, start_datetime: str, end_datetime: str) -> Optional[int]: + sql = insert(quests).values( + version = version, + questId = questId, + name = name, + kind = kind, + unknown_0 = unknown_0, + unknown_1 = unknown_1, + unknown_2 = unknown_2, + quest_order = quest_order, + start_datetime = start_datetime, + end_datetime = end_datetime + ) + + conflict = sql.on_duplicate_key_update( + name = name + ) + + result = self.execute(conflict) + if result is None: return None + return result.lastrowid + + def get_enabled_quests(self, version: int) -> Optional[List[Row]]: + sql = select(quests).where(and_(quests.c.version == version, quests.c.quest_enable == True)) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def put_shop(self, version: int, shopId: int, name: str, type: int, points: int, unknown_0: int, start_date: str, end_date: str) -> Optional[int]: + sql = insert(shop).values( + version = version, + shopId = shopId, + name = name, + type = type, + points = points, + unknown_0 = unknown_0, + start_date = start_date, + end_date = end_date + ) + + conflict = sql.on_duplicate_key_update( + name = name + ) + + result = self.execute(conflict) + if result is None: return None + return result.lastrowid + + def get_enabled_shop(self, version: int) -> Optional[List[Row]]: + sql = select(shop).where(and_(shop.c.version == version, shop.c.enabled == True)) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def put_items(self, version: int, itemId: int, name: str, type: int, points: int, unknown_0: int, start_date: str, end_date: str) -> Optional[int]: + sql = insert(items).values( + version = version, + itemId = itemId, + name = name, + type = type, + points = points, + unknown_0 = unknown_0, + start_date = start_date, + end_date = end_date + ) + + conflict = sql.on_duplicate_key_update( + name = name + ) + + result = self.execute(conflict) + if result is None: return None + return result.lastrowid + + def get_enabled_items(self, version: int) -> Optional[List[Row]]: + sql = select(items).where(and_(items.c.version == version, items.c.enabled == True)) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def put_music(self, version: int, song: int, chart: int, title: str, arranger: str, illustrator: str, + lyrics: str, music_comp: str, level: float, bpm: int, date: str) -> Optional[int]: + sql = insert(music).values( + version = version, + songId = song, + chartId = chart, + title = title, + vocaloid_arranger = arranger, + pv_illustrator = illustrator, + lyrics = lyrics, + bg_music = music_comp, + level = level, + bpm = bpm, + date = date + ) + + conflict = sql.on_duplicate_key_update( + title = title, + vocaloid_arranger = arranger, + pv_illustrator = illustrator, + lyrics = lyrics, + bg_music = music_comp, + level = level, + bpm = bpm, + date = date + ) + + result = self.execute(conflict) + if result is None: return None + return result.lastrowid + + def get_music(self, version: int, song_id: Optional[int] = None) -> Optional[List[Row]]: + if song_id is None: + sql = select(music).where(music.c.version == version) + else: + sql = select(music).where(and_( + music.c.version == version, + music.c.songId == song_id, + )) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def get_music_chart(self, version: int, song_id: int, chart_id: int) -> Optional[List[Row]]: + sql = select(music).where(and_( + music.c.version == version, + music.c.songId == song_id, + music.c.chartId == chart_id + )) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() diff --git a/titles/mai2/__init__.py b/titles/mai2/__init__.py new file mode 100644 index 0000000..1c51244 --- /dev/null +++ b/titles/mai2/__init__.py @@ -0,0 +1,18 @@ +from titles.mai2.index import Mai2Servlet +from titles.mai2.const import Mai2Constants +from titles.mai2.database import Mai2Data +from titles.mai2.read import Mai2Reader + +index = Mai2Servlet +database = Mai2Data +reader = Mai2Reader + +use_default_title = True +include_protocol = True +title_secure = False +game_codes = [Mai2Constants.GAME_CODE] +trailing_slash = True +use_default_host = False +host = "" + +current_schema_version = 1 \ No newline at end of file diff --git a/titles/mai2/base.py b/titles/mai2/base.py new file mode 100644 index 0000000..4b0b81c --- /dev/null +++ b/titles/mai2/base.py @@ -0,0 +1,477 @@ +from datetime import datetime, date, timedelta +from typing import Dict +import logging + +from core.config import CoreConfig +from titles.mai2.const import Mai2Constants +from titles.mai2.config import Mai2Config +from titles.mai2.database import Mai2Data + +class Mai2Base(): + def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None: + self.core_config = cfg + self.game_config = game_cfg + self.game = Mai2Constants.GAME_CODE + self.version = Mai2Constants.VER_MAIMAI_DX + self.data = Mai2Data(cfg) + self.logger = logging.getLogger("mai2") + + def handle_get_game_setting_api_request(self, data: Dict): + reboot_start = date.strftime(datetime.now() + timedelta(hours=3), Mai2Constants.DATE_TIME_FORMAT) + reboot_end = date.strftime(datetime.now() + timedelta(hours=4), Mai2Constants.DATE_TIME_FORMAT) + return { + "gameSetting": { + "isMaintenance": "false", + "requestInterval": 10, + "rebootStartTime": reboot_start, + "rebootEndTime": reboot_end, + "movieUploadLimit": 10000, + "movieStatus": 0, + "movieServerUri": "", + "deliverServerUri": "", + "oldServerUri": "", + "usbDlServerUri": "", + "rebootInterval": 0 + }, + "isAouAccession": "true", + } + + def handle_get_game_ranking_api_request(self, data: Dict) -> Dict: + return {"length": 0, "gameRankingList": []} + + def handle_get_game_tournament_info_api_request(self, data: Dict) -> Dict: + # TODO: Tournament support + return {"length": 0, "gameTournamentInfoList": []} + + def handle_get_game_event_api_request(self, data: Dict) -> Dict: + events = self.data.static.get_enabled_events(self.version) + events_lst = [] + if events is None: return {"type": data["type"], "length": 0, "gameEventList": []} + + for event in events: + events_lst.append({ + "type": event["type"], + "id": event["eventId"], + "startDate": "2017-12-05 07:00:00.0", + "endDate": "2099-12-31 00:00:00.0" + }) + + return {"type": data["type"], "length": len(events_lst), "gameEventList": events_lst} + + def handle_get_game_ng_music_id_api_request(self, data: Dict) -> Dict: + return {"length": 0, "musicIdList": []} + + def handle_get_game_charge_api_request(self, data: Dict) -> Dict: + game_charge_list = self.data.static.get_enabled_tickets(self.version, 1) + if game_charge_list is None: return {"length": 0, "gameChargeList": []} + + charge_list = [] + for x in range(len(game_charge_list)): + charge_list.append({ + "orderId": x, + "chargeId": game_charge_list[x]["ticketId"], + "price": game_charge_list[x]["price"], + "startDate": "2017-12-05 07:00:00.0", + "endDate": "2099-12-31 00:00:00.0" + }) + + return {"length": len(charge_list), "gameChargeList": charge_list} + + def handle_upsert_client_setting_api_request(self, data: Dict) -> Dict: + pass + + def handle_upsert_client_upload_api_request(self, data: Dict) -> Dict: + pass + + def handle_upsert_client_bookkeeping_api_request(self, data: Dict) -> Dict: + pass + + def handle_upsert_client_testmode_api_request(self, data: Dict) -> Dict: + pass + + def handle_get_user_preview_api_request(self, data: Dict) -> Dict: + p = self.data.profile.get_profile_detail(data["userId"], self.version) + o = self.data.profile.get_profile_option(data["userId"], self.version) + if p is None or o is None: return {} # Register + profile = p._asdict() + option = o._asdict() + + return { + "userId": data["userId"], + "userName": profile["userName"], + "isLogin": False, + "lastGameId": profile["lastGameId"], + "lastDataVersion": profile["lastDataVersion"], + "lastRomVersion": profile["lastRomVersion"], + "lastLoginDate": profile["lastLoginDate"], + "lastPlayDate": profile["lastPlayDate"], + "playerRating": profile["playerRating"], + "nameplateId": 0, # Unused + "iconId": profile["iconId"], + "trophyId": 0, # Unused + "partnerId": profile["partnerId"], + "frameId": profile["frameId"], + "dispRate": option["dispRate"], # 0: all/begin, 1: disprate, 2: dispDan, 3: hide, 4: end + "totalAwake": profile["totalAwake"], + "isNetMember": profile["isNetMember"], + "dailyBonusDate": profile["dailyBonusDate"], + "headPhoneVolume": option["headPhoneVolume"], + "isInherit": False, # Not sure what this is or does?? + "banState": profile["banState"] if profile["banState"] is not None else 0 # New with uni+ + } + + def handle_user_login_api_request(self, data: Dict) -> Dict: + profile = self.data.profile.get_profile_detail(data["userId"], self.version) + + if profile is not None: + lastLoginDate = profile["lastLoginDate"] + loginCt = profile["playCount"] + + if "regionId" in data: + self.data.profile.put_profile_region(data["userId"], data["regionId"]) + else: + loginCt = 0 + lastLoginDate = "2017-12-05 07:00:00.0" + + return { + "returnCode": 1, + "lastLoginDate": lastLoginDate, + "loginCount": loginCt, + "consecutiveLoginCount": 0, # We don't really have a way to track this... + "loginId": loginCt # Used with the playlog! + } + + def handle_upload_user_playlog_api_request(self, data: Dict) -> Dict: + user_id = data["userId"] + playlog = data["userPlaylog"] + + self.data.score.put_playlog(user_id, playlog) + + def handle_upsert_user_all_api_request(self, data: Dict) -> Dict: + user_id = data["userId"] + upsert = data["upsertUserAll"] + + if "userData" in upsert and len(upsert["userData"]) > 0: + upsert["userData"][0]["isNetMember"] = 1 + upsert["userData"][0].pop("accessCode") + self.data.profile.put_profile_detail(user_id, self.version, upsert["userData"][0]) + + if "userExtend" in upsert and len(upsert["userExtend"]) > 0: + self.data.profile.put_profile_extend(user_id, self.version, upsert["userExtend"][0]) + + if "userGhost" in upsert: + for ghost in upsert["userGhost"]: + self.data.profile.put_profile_extend(user_id, self.version, ghost) + + if "userOption" in upsert and len(upsert["userOption"]) > 0: + self.data.profile.put_profile_option(user_id, self.version, upsert["userOption"][0]) + + if "userRatingList" in upsert and len(upsert["userRatingList"]) > 0: + self.data.profile.put_profile_rating(user_id, self.version, upsert["userRatingList"][0]) + + if "userActivityList" in upsert and len(upsert["userActivityList"]) > 0: + for k,v in upsert["userActivityList"][0].items(): + for act in v: + self.data.profile.put_profile_activity(user_id, act) + + if upsert["isNewCharacterList"] and int(upsert["isNewCharacterList"]) > 0: + for char in upsert["userCharacterList"]: + self.data.item.put_character(user_id, char["characterId"], char["level"], char["awakening"], char["useCount"]) + + if upsert["isNewItemList"] and int(upsert["isNewItemList"]) > 0: + for item in upsert["userItemList"]: + self.data.item.put_item(user_id, int(item["itemKind"]), item["itemId"], item["stock"], item["isValid"]) + + if upsert["isNewLoginBonusList"] and int(upsert["isNewLoginBonusList"]) > 0: + for login_bonus in upsert["userLoginBonusList"]: + self.data.item.put_login_bonus(user_id, login_bonus["bonusId"], login_bonus["point"], login_bonus["isCurrent"], login_bonus["isComplete"]) + + if upsert["isNewMapList"] and int(upsert["isNewMapList"]) > 0: + for map in upsert["userMapList"]: + self.data.item.put_map(user_id, map["mapId"], map["distance"], map["isLock"], map["isClear"], map["isComplete"]) + + if upsert["isNewMusicDetailList"] and int(upsert["isNewMusicDetailList"]) > 0: + for music in upsert["userMusicDetailList"]: + self.data.score.put_best_score(user_id, music) + + if upsert["isNewCourseList"] and int(upsert["isNewCourseList"]) > 0: + for course in upsert["userCourseList"]: + self.data.score.put_course(user_id, course) + + if upsert["isNewFavoriteList"] and int(upsert["isNewFavoriteList"]) > 0: + for fav in upsert["userFavoriteList"]: + self.data.item.put_favorite(user_id, fav["kind"], fav["itemIdList"]) + + if "isNewFriendSeasonRankingList" in upsert and int(upsert["isNewFriendSeasonRankingList"]) > 0: + for fsr in upsert["userFriendSeasonRankingList"]: + pass + + def handle_user_logout_api_request(self, data: Dict) -> Dict: + pass + + def handle_get_user_data_api_request(self, data: Dict) -> Dict: + profile = self.data.profile.get_profile_detail(data["userId"], self.version) + if profile is None: return + + profile_dict = profile._asdict() + profile_dict.pop("id") + profile_dict.pop("user") + profile_dict.pop("version") + + return { + "userId": data["userId"], + "userData": profile_dict + } + + def handle_get_user_extend_api_request(self, data: Dict) -> Dict: + extend = self.data.profile.get_profile_extend(data["userId"], self.version) + if extend is None: return + + extend_dict = extend._asdict() + extend_dict.pop("id") + extend_dict.pop("user") + extend_dict.pop("version") + + return { + "userId": data["userId"], + "userExtend": extend_dict + } + + def handle_get_user_option_api_request(self, data: Dict) -> Dict: + options = self.data.profile.get_profile_option(data["userId"], self.version) + if options is None: return + + options_dict = options._asdict() + options_dict.pop("id") + options_dict.pop("user") + options_dict.pop("version") + + return { + "userId": data["userId"], + "userOption": options_dict + } + + def handle_get_user_card_api_request(self, data: Dict) -> Dict: + return {"userId": data["userId"], "nextIndex": 0, "userCardList": []} + + def handle_get_user_charge_api_request(self, data: Dict) -> Dict: + return {"userId": data["userId"], "length": 0, "userChargeList": []} + + def handle_get_user_item_api_request(self, data: Dict) -> Dict: + kind = int(data["nextIndex"] / 10000000000) + next_idx = int(data["nextIndex"] % 10000000000) + user_items = self.data.item.get_items(data["userId"], kind) + user_item_list = [] + next_idx = 0 + + for x in range(next_idx, data["maxCount"]): + try: + user_item_list.append({"item_kind": user_items[x]["item_kind"], "item_id": user_items[x]["item_id"], + "stock": user_items[x]["stock"], "isValid": user_items[x]["is_valid"]}) + except: break + + if len(user_item_list) == data["maxCount"]: + next_idx = data["nextIndex"] + data["maxCount"] + 1 + break + + return {"userId": data["userId"], "nextIndex": next_idx, "itemKind": kind, "userItemList": user_item_list} + + def handle_get_user_character_api_request(self, data: Dict) -> Dict: + characters = self.data.item.get_characters(data["userId"]) + chara_list = [] + for chara in characters: + chara_list.append({ + "characterId": chara["character_id"], + "level": chara["level"], + "awakening": chara["awakening"], + "useCount": chara["use_count"], + }) + + return {"userId": data["userId"], "userCharacterList": chara_list} + + def handle_get_user_favorite_api_request(self, data: Dict) -> Dict: + favorites = self.data.item.get_favorites(data["userId"], data["itemKind"]) + if favorites is None: return + + userFavs = [] + for fav in favorites: + userFavs.append({ + "userId": data["userId"], + "itemKind": fav["itemKind"], + "itemIdList": fav["itemIdList"] + }) + + return { + "userId": data["userId"], + "userFavoriteData": userFavs + } + + def handle_get_user_ghost_api_request(self, data: Dict) -> Dict: + ghost = self.data.profile.get_profile_ghost(data["userId"], self.version) + if ghost is None: return + + ghost_dict = ghost._asdict() + ghost_dict.pop("user") + ghost_dict.pop("id") + ghost_dict.pop("version_int") + + return { + "userId": data["userId"], + "userGhost": ghost_dict + } + + def handle_get_user_rating_api_request(self, data: Dict) -> Dict: + rating = self.data.profile.get_profile_rating(data["userId"], self.version) + if rating is None: return + + rating_dict = rating._asdict() + rating_dict.pop("user") + rating_dict.pop("id") + rating_dict.pop("version") + + return { + "userId": data["userId"], + "userRating": rating_dict + } + + def handle_get_user_activity_api_request(self, data: Dict) -> Dict: + """ + kind 1 is playlist, kind 2 is music list + """ + playlist = self.data.profile.get_profile_activity(data["userId"], 1) + musiclist = self.data.profile.get_profile_activity(data["userId"], 2) + if playlist is None or musiclist is None: return + + plst = [] + mlst = [] + + for play in playlist: + tmp = play._asdict() + tmp["id"] = tmp["activityId"] + tmp.pop("activityId") + tmp.pop("user") + plst.append(tmp) + + for music in musiclist: + tmp = music._asdict() + tmp["id"] = tmp["activityId"] + tmp.pop("activityId") + tmp.pop("user") + mlst.append(tmp) + + return { + "userActivity": { + "playList": plst, + "musicList": mlst + } + } + + def handle_get_user_course_api_request(self, data: Dict) -> Dict: + user_courses = self.data.score.get_courses(data["userId"]) + + course_list = [] + for course in user_courses: + tmp = course._asdict() + tmp.pop("user") + tmp.pop("id") + course_list.append(tmp) + + return {"userId": data["userId"], "nextIndex": 0, "userCourseList": course_list} + + def handle_get_user_portrait_api_request(self, data: Dict) -> Dict: + # No support for custom pfps + return {"length": 0, "userPortraitList": []} + + def handle_get_user_friend_season_ranking_api_request(self, data: Dict) -> Dict: + friend_season_ranking = self.data.item.get_friend_season_ranking(data["userId"]) + friend_season_ranking_list = [] + next_index = 0 + + for x in range(data["nextIndex"], data["maxCount"] + data["nextIndex"]): + try: + friend_season_ranking_list.append({ + "mapId": friend_season_ranking_list[x]["map_id"], + "distance": friend_season_ranking_list[x]["distance"], + "isLock": friend_season_ranking_list[x]["is_lock"], + "isClear": friend_season_ranking_list[x]["is_clear"], + "isComplete": friend_season_ranking_list[x]["is_complete"], + }) + except: + break + + # We're capped and still have some left to go + if len(friend_season_ranking_list) == data["maxCount"] and len(friend_season_ranking) > data["maxCount"] + data["nextIndex"]: + next_index = data["maxCount"] + data["nextIndex"] + + return {"userId": data["userId"], "nextIndex": next_index, "userFriendSeasonRankingList": friend_season_ranking_list} + + def handle_get_user_map_api_request(self, data: Dict) -> Dict: + maps = self.data.item.get_maps(data["userId"]) + map_list = [] + next_index = 0 + + for x in range(data["nextIndex"], data["maxCount"] + data["nextIndex"]): + try: + map_list.append({ + "mapId": maps[x]["map_id"], + "distance": maps[x]["distance"], + "isLock": maps[x]["is_lock"], + "isClear": maps[x]["is_clear"], + "isComplete": maps[x]["is_complete"], + }) + except: + break + + # We're capped and still have some left to go + if len(map_list) == data["maxCount"] and len(maps) > data["maxCount"] + data["nextIndex"]: + next_index = data["maxCount"] + data["nextIndex"] + + return {"userId": data["userId"], "nextIndex": next_index, "userMapList": map_list} + + def handle_get_user_login_bonus_api_request(self, data: Dict) -> Dict: + login_bonuses = self.data.item.get_login_bonuses(data["userId"]) + login_bonus_list = [] + next_index = 0 + + for x in range(data["nextIndex"], data["maxCount"] + data["nextIndex"]): + try: + login_bonus_list.append({ + "bonusId": login_bonuses[x]["bonus_id"], + "point": login_bonuses[x]["point"], + "isCurrent": login_bonuses[x]["is_current"], + "isComplete": login_bonuses[x]["is_complete"], + }) + except: + break + + # We're capped and still have some left to go + if len(login_bonus_list) == data["maxCount"] and len(login_bonuses) > data["maxCount"] + data["nextIndex"]: + next_index = data["maxCount"] + data["nextIndex"] + + return {"userId": data["userId"], "nextIndex": next_index, "userLoginBonusList": login_bonus_list} + + def handle_get_user_region_api_request(self, data: Dict) -> Dict: + return {"userId": data["userId"], "length": 0, "userRegionList": []} + + def handle_get_user_music_api_request(self, data: Dict) -> Dict: + songs = self.data.score.get_best_scores(data["userId"]) + music_detail_list = [] + next_index = 0 + + if songs is not None: + for song in songs: + music_detail_list.append({ + "musicId": song["song_id"], + "level": song["chart_id"], + "playCount": song["play_count"], + "achievement": song["achievement"], + "comboStatus": song["combo_status"], + "syncStatus": song["sync_status"], + "deluxscoreMax": song["dx_score"], + "scoreRank": song["score_rank"], + }) + if len(music_detail_list) == data["maxCount"]: + next_index = data["maxCount"] + data["nextIndex"] + break + + return {"userId": data["userId"], "nextIndex": next_index, "userMusicList": [{"userMusicDetailList": music_detail_list}]} diff --git a/titles/mai2/config.py b/titles/mai2/config.py new file mode 100644 index 0000000..7fe9a33 --- /dev/null +++ b/titles/mai2/config.py @@ -0,0 +1,17 @@ +from core.config import CoreConfig + +class Mai2ServerConfig(): + def __init__(self, parent: "Mai2Config") -> None: + self.__config = parent + + @property + def enable(self) -> bool: + return CoreConfig.get_config_field(self.__config, 'mai2', 'server', 'enable', default=True) + + @property + def loglevel(self) -> int: + return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'mai2', 'server', 'loglevel', default="info")) + +class Mai2Config(dict): + def __init__(self) -> None: + self.server = Mai2ServerConfig(self) \ No newline at end of file diff --git a/titles/mai2/const.py b/titles/mai2/const.py new file mode 100644 index 0000000..c6ae129 --- /dev/null +++ b/titles/mai2/const.py @@ -0,0 +1,51 @@ +class Mai2Constants(): + GRADE = { + "D": 0, + "C": 1, + "B": 2, + "BB": 3, + "BBB": 4, + "A": 5, + "AA": 6, + "AAA": 7, + "S": 8, + "S+": 9, + "SS": 10, + "SS+": 11, + "SSS": 12, + "SSS+": 13 + } + FC = { + "None": 0, + "FC": 1, + "FC+": 2, + "AP": 3, + "AP+": 4 + } + SYNC = { + "None": 0, + "FS": 1, + "FS+": 2, + "FDX": 3, + "FDX+": 4 + } + + DATE_TIME_FORMAT = "%Y-%m-%d %H:%M:%S" + + GAME_CODE = "SDEZ" + + CONFIG_NAME = "mai2.yaml" + + VER_MAIMAI_DX = 0 + VER_MAIMAI_DX_PLUS = 1 + VER_MAIMAI_DX_SPLASH = 2 + VER_MAIMAI_DX_SPLASH_PLUS = 3 + VER_MAIMAI_DX_UNIVERSE = 4 + VER_MAIMAI_DX_UNIVERSE_PLUS = 5 + + VERSION_STRING = ("maimai Delux", "maimai Delux PLUS", "maimai Delux Splash", "maimai Delux Splash PLUS", "maimai Delux Universe", + "maimai Delux Universe PLUS") + + @classmethod + def game_ver_to_string(cls, ver: int): + return cls.VERSION_STRING[ver] \ No newline at end of file diff --git a/titles/mai2/database.py b/titles/mai2/database.py new file mode 100644 index 0000000..7a19e75 --- /dev/null +++ b/titles/mai2/database.py @@ -0,0 +1,12 @@ +from core.data import Data +from core.config import CoreConfig +from titles.mai2.schema import Mai2ItemData, Mai2ProfileData, Mai2StaticData, Mai2ScoreData + +class Mai2Data(Data): + def __init__(self, cfg: CoreConfig) -> None: + super().__init__(cfg) + + self.profile = Mai2ProfileData(self.config, self.session) + self.item = Mai2ItemData(self.config, self.session) + self.static = Mai2StaticData(self.config, self.session) + self.score = Mai2ScoreData(self.config, self.session) \ No newline at end of file diff --git a/titles/mai2/index.py b/titles/mai2/index.py new file mode 100644 index 0000000..a4a8f3e --- /dev/null +++ b/titles/mai2/index.py @@ -0,0 +1,109 @@ +from twisted.web.http import Request +import json +import inflection +import yaml +import string +import logging, coloredlogs +import zlib +from logging.handlers import TimedRotatingFileHandler + +from core.config import CoreConfig +from titles.mai2.config import Mai2Config +from titles.mai2.const import Mai2Constants +from titles.mai2.base import Mai2Base +from titles.mai2.plus import Mai2Plus +from titles.mai2.splash import Mai2Splash +from titles.mai2.splashplus import Mai2SplashPlus +from titles.mai2.universe import Mai2Universe +from titles.mai2.universeplus import Mai2UniversePlus + + +class Mai2Servlet(): + def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None: + self.core_cfg = core_cfg + self.game_cfg = Mai2Config() + self.game_cfg.update(yaml.safe_load(open(f"{cfg_dir}/{Mai2Constants.CONFIG_NAME}"))) + + self.versions = [ + Mai2Base(core_cfg, self.game_cfg), + Mai2Plus(core_cfg, self.game_cfg), + Mai2Splash(core_cfg, self.game_cfg), + Mai2SplashPlus(core_cfg, self.game_cfg), + Mai2Universe(core_cfg, self.game_cfg), + Mai2UniversePlus(core_cfg, self.game_cfg), + ] + + self.logger = logging.getLogger("mai2") + log_fmt_str = "[%(asctime)s] Mai2 | %(levelname)s | %(message)s" + log_fmt = logging.Formatter(log_fmt_str) + fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.core_cfg.server.log_dir, "mai2"), encoding='utf8', + when="d", backupCount=10) + + fileHandler.setFormatter(log_fmt) + + consoleHandler = logging.StreamHandler() + consoleHandler.setFormatter(log_fmt) + + self.logger.addHandler(fileHandler) + self.logger.addHandler(consoleHandler) + + self.logger.setLevel(self.game_cfg.server.loglevel) + coloredlogs.install(level=self.game_cfg.server.loglevel, logger=self.logger, fmt=log_fmt_str) + + def render_POST(self, request: Request, version: int, url_path: str) -> bytes: + req_raw = request.content.getvalue() + url = request.uri.decode() + url_split = url_path.split("/") + internal_ver = 0 + endpoint = url_split[len(url_split) - 1] + + if version < 105: # 1.0 + internal_ver = Mai2Constants.VER_MAIMAI_DX + elif version >= 105 and version < 110: # Plus + internal_ver = Mai2Constants.VER_MAIMAI_DX_PLUS + elif version >= 110 and version < 115: # Splash + internal_ver = Mai2Constants.VER_MAIMAI_DX_SPLASH + elif version >= 115 and version < 120: # Splash Plus + internal_ver = Mai2Constants.VER_MAIMAI_DX_SPLASH_PLUS + elif version >= 120 and version < 125: # Universe + internal_ver = Mai2Constants.VER_MAIMAI_DX_UNIVERSE + elif version >= 125: # Universe Plus + internal_ver = Mai2Constants.VER_MAIMAI_DX_UNIVERSE_PLUS + + if all(c in string.hexdigits for c in endpoint) and len(endpoint) == 32: + # If we get a 32 character long hex string, it's a hash and we're + # doing encrypted. The likelyhood of false positives is low but + # technically not 0 + self.logger.error("Encryption not supported at this time") + + try: + unzip = zlib.decompress(req_raw) + + except zlib.error as e: + self.logger.error(f"Failed to decompress v{version} {endpoint} request -> {e}") + return zlib.compress("{\"stat\": \"0\"}".encode("utf-8")) + + req_data = json.loads(unzip) + + self.logger.info(f"v{version} {endpoint} request - {req_data}") + + func_to_find = "handle_" + inflection.underscore(endpoint) + "_request" + + try: + handler = getattr(self.versions[internal_ver], func_to_find) + 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: + self.logger.error(f"Error handling v{version} method {endpoint} - {e}") + return zlib.compress("{\"stat\": \"0\"}".encode("utf-8")) + + if resp == None: + resp = {'returnCode': 1} + + self.logger.info(f"Response {resp}") + + return zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8")) diff --git a/titles/mai2/plus.py b/titles/mai2/plus.py new file mode 100644 index 0000000..2af7bf6 --- /dev/null +++ b/titles/mai2/plus.py @@ -0,0 +1,14 @@ +from typing import Any, List, Dict +from datetime import datetime, timedelta +import pytz +import json + +from core.config import CoreConfig +from titles.mai2.base import Mai2Base +from titles.mai2.config import Mai2Config +from titles.mai2.const import Mai2Constants + +class Mai2Plus(Mai2Base): + def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None: + super().__init__(cfg, game_cfg) + self.version = Mai2Constants.VER_MAIMAI_DX_PLUS \ No newline at end of file diff --git a/titles/mai2/read.py b/titles/mai2/read.py new file mode 100644 index 0000000..1652292 --- /dev/null +++ b/titles/mai2/read.py @@ -0,0 +1,103 @@ +from decimal import Decimal +import logging +import os +import re +import xml.etree.ElementTree as ET +from typing import Any, Dict, List, Optional + +from core.config import CoreConfig +from core.data import Data +from read import BaseReader +from titles.mai2.const import Mai2Constants +from titles.mai2.database import Mai2Data + +class Mai2Reader(BaseReader): + def __init__(self, config: CoreConfig, version: int, bin_dir: Optional[str], opt_dir: Optional[str], extra: Optional[str]) -> None: + super().__init__(config, version, bin_dir, opt_dir, extra) + self.data = Mai2Data(config) + + try: + self.logger.info(f"Start importer for {Mai2Constants.game_ver_to_string(version)}") + except IndexError: + self.logger.error(f"Invalid maidx version {version}") + exit(1) + + def read(self) -> None: + data_dirs = [] + if self.bin_dir is not None: + data_dirs += self.get_data_directories(self.bin_dir) + + if self.opt_dir is not None: + data_dirs += self.get_data_directories(self.opt_dir) + + for dir in data_dirs: + self.logger.info(f"Read from {dir}") + self.get_events(f"{dir}/event") + self.read_music(f"{dir}/music") + self.read_tickets(f"{dir}/ticket") + + def get_events(self, base_dir: str) -> None: + self.logger.info(f"Reading events from {base_dir}...") + + for root, dirs, files in os.walk(base_dir): + for dir in dirs: + if os.path.exists(f"{root}/{dir}/Event.xml"): + with open(f"{root}/{dir}/Event.xml", encoding="utf-8") as f: + + troot = ET.fromstring(f.read()) + + name = troot.find('name').find('str').text + id = int(troot.find('name').find('id').text) + event_type = int(troot.find('infoType').text) + + self.data.static.put_game_event(self.version, event_type, id, name) + self.logger.info(f"Added event {id}...") + + def read_music(self, base_dir: str) -> None: + self.logger.info(f"Reading music from {base_dir}...") + + for root, dirs, files in os.walk(base_dir): + for dir in dirs: + if os.path.exists(f"{root}/{dir}/Music.xml"): + with open(f"{root}/{dir}/Music.xml", encoding="utf-8") as f: + troot = ET.fromstring(f.read()) + + song_id = int(troot.find('name').find('id').text) + title = troot.find('name').find('str').text + artist = troot.find('artistName').find('str').text + genre = troot.find('genreName').find('str').text + bpm = int(troot.find('bpm').text) + added_ver = troot.find('AddVersion').find('str').text + + note_data = troot.find('notesData').findall('Notes') + + for dif in note_data: + path = dif.find('file').find('path').text + if path is not None: + if os.path.exists(f"{root}/{dir}/{path}"): + chart_id = int(path.split(".")[0].split('_')[1]) + diff_num = float(f"{dif.find('level').text}.{dif.find('levelDecimal').text}") + note_designer = dif.find('notesDesigner').find('str').text + + self.data.static.put_game_music(self.version, song_id, chart_id, title, artist, + genre, bpm, added_ver, diff_num, note_designer) + + self.logger.info(f"Added music id {song_id} chart {chart_id}") + + def read_tickets(self, base_dir: str) -> None: + self.logger.info(f"Reading tickets from {base_dir}...") + + for root, dirs, files in os.walk(base_dir): + for dir in dirs: + if os.path.exists(f"{root}/{dir}/Ticket.xml"): + with open(f"{root}/{dir}/Ticket.xml", encoding="utf-8") as f: + + troot = ET.fromstring(f.read()) + + name = troot.find('name').find('str').text + id = int(troot.find('name').find('id').text) + ticket_type = int(troot.find('ticketKind').find('id').text) + price = int(troot.find('creditNum').text) + + self.data.static.put_game_ticket(self.version, id, ticket_type, price, name) + self.logger.info(f"Added ticket {id}...") diff --git a/titles/mai2/schema/__init__.py b/titles/mai2/schema/__init__.py new file mode 100644 index 0000000..c2be969 --- /dev/null +++ b/titles/mai2/schema/__init__.py @@ -0,0 +1,6 @@ +from titles.mai2.schema.profile import Mai2ProfileData +from titles.mai2.schema.item import Mai2ItemData +from titles.mai2.schema.static import Mai2StaticData +from titles.mai2.schema.score import Mai2ScoreData + +__all__ = [Mai2ProfileData, Mai2ItemData, Mai2StaticData, Mai2ScoreData] \ No newline at end of file diff --git a/titles/mai2/schema/item.py b/titles/mai2/schema/item.py new file mode 100644 index 0000000..4f283d3 --- /dev/null +++ b/titles/mai2/schema/item.py @@ -0,0 +1,298 @@ +from core.data.schema import BaseData, metadata + +from typing import Optional, Dict, List +from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_ +from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON +from sqlalchemy.schema import ForeignKey +from sqlalchemy.sql import func, select +from sqlalchemy.dialects.mysql import insert +from sqlalchemy.engine import Row + +character = Table( + "mai2_item_character", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("character_id", Integer, nullable=False), + Column("level", Integer, nullable=False, server_default="1"), + Column("awakening", Integer, nullable=False, server_default="0"), + Column("use_count", Integer, nullable=False, server_default="0"), + UniqueConstraint("user", "character_id", name="mai2_item_character_uk"), + mysql_charset='utf8mb4' +) + +card = Table( + "mai2_item_card", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("card_kind", Integer, nullable=False), + Column("card_id", Integer, nullable=False), + Column("chara_id", Integer, nullable=False), + Column("map_id", Integer, nullable=False), + Column("start_date", String(255), nullable=False), + Column("end_date", String(255), nullable=False), + UniqueConstraint("user", "card_kind", "card_id", name="mai2_item_card_uk"), + mysql_charset='utf8mb4' +) + +item = Table( + "mai2_item_item", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("item_kind", Integer, nullable=False), + Column("item_id", Integer, nullable=False), + Column("stock", Integer, nullable=False, server_default="1"), + Column("is_valid", Boolean, nullable=False, server_default="1"), + UniqueConstraint("user", "item_kind", "item_id", name="mai2_item_item_uk"), + mysql_charset='utf8mb4' +) + +map = Table( + "mai2_item_map", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("map_id", Integer, nullable=False), + Column("distance", Integer, nullable=False), + Column("is_lock", Boolean, nullable=False, server_default="0"), + Column("is_clear", Boolean, nullable=False, server_default="0"), + Column("is_complete", Boolean, nullable=False, server_default="0"), + UniqueConstraint("user", "map_id", name="mai2_item_map_uk"), + mysql_charset='utf8mb4' +) + +login_bonus = Table( + "mai2_item_login_bonus", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("bonus_id", Integer, nullable=False), + Column("point", Integer, nullable=False), + Column("is_current", Boolean, nullable=False, server_default="0"), + Column("is_complete", Boolean, nullable=False, server_default="0"), + UniqueConstraint("user", "bonus_id", name="mai2_item_login_bonus_uk"), + mysql_charset='utf8mb4' +) + +friend_season_ranking = Table( + "mai2_item_friend_season_ranking", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("season_id", Integer, nullable=False), + Column("point", Integer, nullable=False), + Column("rank", Integer, nullable=False), + Column("reward_get", Boolean, nullable=False), + Column("user_name", String(8), nullable=False), + Column("record_date", String(255), nullable=False), + UniqueConstraint("user", "season_id", "user_name", name="mai2_item_login_bonus_uk"), + mysql_charset='utf8mb4' +) + +favorite = Table( + "mai2_item_favorite", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("itemKind", Integer, nullable=False), + Column("itemIdList", JSON), + UniqueConstraint("user", "itemKind", name="mai2_item_favorite_uk"), + mysql_charset='utf8mb4' +) + +charge = Table( + "mai2_item_charge", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("charge_id", Integer, nullable=False), + Column("stock", Integer, nullable=False), + Column("purchase_date", String(255), nullable=False), + Column("valid_date", String(255), nullable=False), + UniqueConstraint("user", "charge_id", name="mai2_item_charge_uk"), + mysql_charset='utf8mb4' +) + +class Mai2ItemData(BaseData): + def put_item(self, user_id: int, item_kind: int, item_id: int, stock: int, is_valid: bool) -> None: + sql = insert(item).values( + user=user_id, + item_kind=item_kind, + item_id=item_id, + stock=stock, + is_valid=is_valid, + ) + + conflict = sql.on_duplicate_key_update( + stock=stock, + is_valid=is_valid, + ) + + result = self.execute(conflict) + if result is None: + self.logger.warn(f"put_item: failed to insert item! user_id: {user_id}, item_kind: {item_kind}, item_id: {item_id}") + return None + return result.lastrowid + + def get_items(self, user_id: int, item_kind: int = None) -> Optional[List[Row]]: + if item_kind is None: + sql = item.select(item.c.user == user_id) + else: + sql = item.select(and_(item.c.user == user_id, item.c.item_kind == item_kind)) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def get_item(self, user_id: int, item_kind: int, item_id: int) -> Optional[Row]: + sql = item.select(and_(item.c.user == user_id, item.c.item_kind == item_kind, item.c.item_id == item_id)) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() + + def put_login_bonus(self, user_id: int, bonus_id: int, point: int, is_current: bool, is_complete: bool) -> None: + sql = insert(login_bonus).values( + user=user_id, + bonus_id=bonus_id, + point=point, + is_current=is_current, + is_complete=is_complete, + ) + + conflict = sql.on_duplicate_key_update( + point=point, + is_current=is_current, + is_complete=is_complete, + ) + + result = self.execute(conflict) + if result is None: + self.logger.warn(f"put_login_bonus: failed to insert item! user_id: {user_id}, bonus_id: {bonus_id}, point: {point}") + return None + return result.lastrowid + + def get_login_bonuses(self, user_id: int) -> Optional[List[Row]]: + sql = login_bonus.select(login_bonus.c.user == user_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def get_login_bonus(self, user_id: int, bonus_id: int) -> Optional[Row]: + sql = login_bonus.select(and_(login_bonus.c.user == user_id, login_bonus.c.bonus_id == bonus_id)) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() + + def put_map(self, user_id: int, map_id: int, distance: int, is_lock: bool, is_clear: bool, is_complete: bool) -> None: + sql = insert(map).values( + user=user_id, + map_id=map_id, + distance=distance, + is_lock=is_lock, + is_clear=is_clear, + is_complete=is_complete, + ) + + conflict = sql.on_duplicate_key_update( + distance=distance, + is_lock=is_lock, + is_clear=is_clear, + is_complete=is_complete, + ) + + result = self.execute(conflict) + if result is None: + self.logger.warn(f"put_map: failed to insert item! user_id: {user_id}, map_id: {map_id}, distance: {distance}") + return None + return result.lastrowid + + def get_maps(self, user_id: int) -> Optional[List[Row]]: + sql = map.select(map.c.user == user_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def get_map(self, user_id: int, map_id: int) -> Optional[Row]: + sql = map.select(and_(map.c.user == user_id, map.c.map_id == map_id)) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() + + def put_character(self, user_id: int, character_id: int, level: int, awakening: int, use_count: int) -> None: + sql = insert(character).values( + user=user_id, + character_id=character_id, + level=level, + awakening=awakening, + use_count=use_count, + ) + + conflict = sql.on_duplicate_key_update( + level=level, + awakening=awakening, + use_count=use_count, + ) + + result = self.execute(conflict) + if result is None: + self.logger.warn(f"put_character: failed to insert item! user_id: {user_id}, character_id: {character_id}, level: {level}") + return None + return result.lastrowid + + def get_characters(self, user_id: int) -> Optional[List[Row]]: + sql = character.select(character.c.user == user_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def get_character(self, user_id: int, character_id: int) -> Optional[Row]: + sql = character.select(and_(character.c.user == user_id, character.c.character_id == character_id)) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() + + def get_friend_season_ranking(self, user_id: int) -> Optional[Row]: + sql = friend_season_ranking.select(friend_season_ranking.c.user == user_id) + + result = self.execute(sql) + if result is None:return None + return result.fetchone() + + def put_favorite(self, user_id: int, kind: int, item_id_list: List[int]) -> Optional[int]: + sql = insert(favorite).values( + user=user_id, + kind=kind, + item_id_list=item_id_list + ) + + conflict = sql.on_duplicate_key_update( + item_id_list=item_id_list + ) + + result = self.execute(conflict) + if result is None: + self.logger.warn(f"put_favorite: failed to insert item! user_id: {user_id}, kind: {kind}") + return None + return result.lastrowid + + def get_favorites(self, user_id: int, kind: int = None) -> Optional[Row]: + if kind is None: + sql = favorite.select(favorite.c.user == user_id) + else: + sql = favorite.select(and_( + favorite.c.user == user_id, + favorite.c.itemKind == kind + )) + + result = self.execute(sql) + if result is None:return None + return result.fetchall() \ No newline at end of file diff --git a/titles/mai2/schema/profile.py b/titles/mai2/schema/profile.py new file mode 100644 index 0000000..346645c --- /dev/null +++ b/titles/mai2/schema/profile.py @@ -0,0 +1,402 @@ +from core.data.schema import BaseData, metadata +from titles.mai2.const import Mai2Constants + +from typing import Optional, Dict, List +from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_ +from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, BigInteger +from sqlalchemy.schema import ForeignKey +from sqlalchemy.sql import func, select +from sqlalchemy.engine import Row +from sqlalchemy.dialects.mysql import insert +from datetime import datetime + +detail = Table( + "mai2_profile_detail", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("version", Integer, nullable=False), + Column("userName", String(25)), + Column("isNetMember", Integer), + Column("iconId", Integer), + Column("plateId", Integer), + Column("titleId", Integer), + Column("partnerId", Integer), + Column("frameId", Integer), + Column("selectMapId", Integer), + Column("totalAwake", Integer), + Column("gradeRating", Integer), + Column("musicRating", Integer), + Column("playerRating", Integer), + Column("highestRating", Integer), + Column("gradeRank", Integer), + Column("classRank", Integer), + Column("courseRank", Integer), + Column("charaSlot", JSON), + Column("charaLockSlot", JSON), + Column("contentBit", BigInteger), + Column("playCount", Integer), + Column("eventWatchedDate", String(25)), + Column("lastGameId", String(25)), + Column("lastRomVersion", String(25)), + Column("lastDataVersion", String(25)), + Column("lastLoginDate", String(25)), + Column("lastPairLoginDate", String(25)), # new with uni+ + Column("lastPlayDate", String(25)), + Column("lastTrialPlayDate", String(25)), # new with uni+ + Column("lastPlayCredit", Integer), + Column("lastPlayMode", Integer), + Column("lastPlaceId", Integer), + Column("lastPlaceName", String(25)), + Column("lastAllNetId", Integer), + Column("lastRegionId", Integer), + Column("lastRegionName", String(25)), + Column("lastClientId", String(25)), + Column("lastCountryCode", String(25)), + Column("lastSelectEMoney", Integer), + Column("lastSelectTicket", Integer), + Column("lastSelectCourse", Integer), + Column("lastCountCourse", Integer), + Column("firstGameId", String(25)), + Column("firstRomVersion", String(25)), + Column("firstDataVersion", String(25)), + Column("firstPlayDate", String(25)), + Column("compatibleCmVersion", String(25)), + Column("dailyBonusDate", String(25)), + Column("dailyCourseBonusDate", String(25)), + Column("playVsCount", Integer), + Column("playSyncCount", Integer), + Column("winCount", Integer), + Column("helpCount", Integer), + Column("comboCount", Integer), + Column("totalDeluxscore", BigInteger), + Column("totalBasicDeluxscore", BigInteger), + Column("totalAdvancedDeluxscore", BigInteger), + Column("totalExpertDeluxscore", BigInteger), + Column("totalMasterDeluxscore", BigInteger), + Column("totalReMasterDeluxscore", BigInteger), + Column("totalSync", Integer), + Column("totalBasicSync", Integer), + Column("totalAdvancedSync", Integer), + Column("totalExpertSync", Integer), + Column("totalMasterSync", Integer), + Column("totalReMasterSync", Integer), + Column("totalAchievement", BigInteger), + Column("totalBasicAchievement", BigInteger), + Column("totalAdvancedAchievement", BigInteger), + Column("totalExpertAchievement", BigInteger), + Column("totalMasterAchievement", BigInteger), + Column("totalReMasterAchievement", BigInteger), + Column("playerOldRating", BigInteger), + Column("playerNewRating", BigInteger), + Column("dateTime", BigInteger), + Column("banState", Integer), # new with uni+ + UniqueConstraint("user", "version", name="mai2_profile_detail_uk"), + mysql_charset='utf8mb4' +) + +ghost = Table( + "mai2_profile_ghost", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("version_int", Integer, nullable=False), + Column("name", String(25)), + Column("iconId", Integer), + Column("plateId", Integer), + Column("titleId", Integer), + Column("rate", Integer), + Column("udemaeRate", Integer), + Column("courseRank", Integer), + Column("classRank", Integer), + Column("classValue", Integer), + Column("playDatetime", String(25)), + Column("shopId", Integer), + Column("regionCode", Integer), + Column("typeId", Integer), + Column("musicId", Integer), + Column("difficulty", Integer), + Column("version", Integer), + Column("resultBitList", JSON), + Column("resultNum", Integer), + Column("achievement", Integer), + UniqueConstraint("user", "version", "musicId", "difficulty", name="mai2_profile_ghost_uk"), + mysql_charset='utf8mb4' +) + +extend = Table( + "mai2_profile_extend", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("version", Integer, nullable=False), + Column("selectMusicId", Integer), + Column("selectDifficultyId", Integer), + Column("categoryIndex", Integer), + Column("musicIndex", Integer), + Column("extraFlag", Integer), + Column("selectScoreType", Integer), + Column("extendContentBit", BigInteger), + Column("isPhotoAgree", Boolean), + Column("isGotoCodeRead", Boolean), + Column("selectResultDetails", Boolean), + Column("sortCategorySetting", Integer), + Column("sortMusicSetting", Integer), + Column("selectedCardList", JSON), + Column("encountMapNpcList", JSON), + UniqueConstraint("user", "version", name="mai2_profile_extend_uk"), + mysql_charset='utf8mb4' +) + +option = Table( + "mai2_profile_option", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("version", Integer, nullable=False), + Column("selectMusicId", Integer), + Column("optionKind", Integer), + Column("noteSpeed", Integer), + Column("slideSpeed", Integer), + Column("touchSpeed", Integer), + Column("tapDesign", Integer), + Column("holdDesign", Integer), + Column("slideDesign", Integer), + Column("starType", Integer), + Column("outlineDesign", Integer), + Column("noteSize", Integer), + Column("slideSize", Integer), + Column("touchSize", Integer), + Column("starRotate", Integer), + Column("dispCenter", Integer), + Column("dispChain", Integer), + Column("dispRate", Integer), + Column("dispBar", Integer), + Column("touchEffect", Integer), + Column("submonitorAnimation", Integer), + Column("submonitorAchive", Integer), + Column("submonitorAppeal", Integer), + Column("matching", Integer), + Column("trackSkip", Integer), + Column("brightness", Integer), + Column("mirrorMode", Integer), + Column("dispJudge", Integer), + Column("dispJudgePos", Integer), + Column("dispJudgeTouchPos", Integer), + Column("adjustTiming", Integer), + Column("judgeTiming", Integer), + Column("ansVolume", Integer), + Column("tapHoldVolume", Integer), + Column("criticalSe", Integer), + Column("breakSe", Integer), + Column("breakVolume", Integer), + Column("exSe", Integer), + Column("exVolume", Integer), + Column("slideSe", Integer), + Column("slideVolume", Integer), + Column("touchHoldVolume", Integer), + Column("damageSeVolume", Integer), + Column("headPhoneVolume", Integer), + Column("sortTab", Integer), + Column("sortMusic", Integer), + UniqueConstraint("user", "version", name="mai2_profile_option_uk"), + mysql_charset='utf8mb4' +) + +rating = Table( + "mai2_profile_rating", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("version", Integer, nullable=False), + Column("rating", Integer), + Column("ratingList", JSON), + Column("newRatingList", JSON), + Column("nextRatingList", JSON), + Column("nextNewRatingList", JSON), + Column("udemae", JSON), + UniqueConstraint("user", "version", name="mai2_profile_rating_uk"), + mysql_charset='utf8mb4' +) + +region = Table( + "mai2_profile_region", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("regionId", Integer), + Column("playCount", Integer, server_default="1"), + Column("created", String(25)), + UniqueConstraint("user", "regionId", name="mai2_profile_region_uk"), + mysql_charset='utf8mb4' +) + +activity = Table( + "mai2_profile_activity", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("kind", Integer, nullable=False), + Column("activityId", Integer, nullable=False), + Column("param1", Integer, nullable=False), + Column("param2", Integer, nullable=False), + Column("param3", Integer, nullable=False), + Column("param4", Integer, nullable=False), + Column("sortNumber", Integer, nullable=False), + UniqueConstraint("user", "kind", "activityId", name="mai2_profile_activity_uk"), + mysql_charset='utf8mb4' +) + +class Mai2ProfileData(BaseData): + def put_profile_detail(self, user_id: int, version: int, detail_data: Dict) -> Optional[Row]: + detail_data["user"] = user_id + detail_data["version"] = version + sql = insert(detail).values(**detail_data) + + conflict = sql.on_duplicate_key_update(**detail_data) + + result = self.execute(conflict) + if result is None: + self.logger.warn(f"put_profile: Failed to create profile! user_id {user_id}") + return None + return result.lastrowid + + def get_profile_detail(self, user_id: int, version: int) -> Optional[Row]: + sql = select(detail).where(and_(detail.c.user == user_id, detail.c.version == version)) + + result = self.execute(sql) + if result is None:return None + return result.fetchone() + + def put_profile_ghost(self, user_id: int, version: int, ghost_data: Dict) -> Optional[int]: + ghost_data["user"] = user_id + ghost_data["version_int"] = version + + sql = insert(ghost).values(**ghost_data) + conflict = sql.on_duplicate_key_update(**ghost_data) + + result = self.execute(conflict) + if result is None: + self.logger.warn(f"put_profile_ghost: failed to update! {user_id}") + return None + return result.lastrowid + + def get_profile_ghost(self, user_id: int, version: int) -> Optional[Row]: + sql = select(ghost).where(and_(ghost.c.user == user_id, ghost.c.version_int == version)) + + result = self.execute(sql) + if result is None:return None + return result.fetchone() + + def put_profile_extend(self, user_id: int, version: int, extend_data: Dict) -> Optional[int]: + extend_data["user"] = user_id + extend_data["version"] = version + + sql = insert(extend).values(**extend_data) + conflict = sql.on_duplicate_key_update(**extend_data) + + result = self.execute(conflict) + if result is None: + self.logger.warn(f"put_profile_extend: failed to update! {user_id}") + return None + return result.lastrowid + + def get_profile_extend(self, user_id: int, version: int) -> Optional[Row]: + sql = select(extend).where(and_(extend.c.user == user_id, extend.c.version == version)) + + result = self.execute(sql) + if result is None:return None + return result.fetchone() + + def put_profile_option(self, user_id: int, version: int, option_data: Dict) -> Optional[int]: + option_data["user"] = user_id + option_data["version"] = version + + sql = insert(option).values(**option_data) + conflict = sql.on_duplicate_key_update(**option_data) + + result = self.execute(conflict) + if result is None: + self.logger.warn(f"put_profile_option: failed to update! {user_id}") + return None + return result.lastrowid + + def get_profile_option(self, user_id: int, version: int) -> Optional[Row]: + sql = select(option).where(and_(option.c.user == user_id, option.c.version == version)) + + result = self.execute(sql) + if result is None:return None + return result.fetchone() + + def put_profile_rating(self, user_id: int, version: int, rating_data: Dict) -> Optional[int]: + rating_data["user"] = user_id + rating_data["version"] = version + + sql = insert(rating).values(**rating_data) + conflict = sql.on_duplicate_key_update(**rating_data) + + result = self.execute(conflict) + if result is None: + self.logger.warn(f"put_profile_rating: failed to update! {user_id}") + return None + return result.lastrowid + + def get_profile_rating(self, user_id: int, version: int) -> Optional[Row]: + sql = select(rating).where(and_(rating.c.user == user_id, rating.c.version == version)) + + result = self.execute(sql) + if result is None:return None + return result.fetchone() + + def put_profile_region(self, user_id: int, region_id: int) -> Optional[int]: + sql = insert(region).values( + user = user_id, + regionId = region_id, + created = datetime.strftime(datetime.now(), Mai2Constants.DATE_TIME_FORMAT) + ) + + conflict = sql.on_duplicate_key_update( + playCount = region.c.playCount + 1 + ) + + result = self.execute(conflict) + if result is None: + self.logger.warn(f"put_region: failed to update! {user_id}") + return None + return result.lastrowid + + def get_regions(self, user_id: int) -> Optional[List[Dict]]: + sql = select(region).where(region.c.user == user_id) + + result = self.execute(sql) + if result is None:return None + return result.fetchall() + + def put_profile_activity(self, user_id: int, activity_data: Dict) -> Optional[int]: + if "id" in activity_data: + activity_data["activityId"] = activity_data["id"] + activity_data.pop("id") + + activity_data["user"] = user_id + + sql = insert(activity).values(**activity_data) + + conflict = sql.on_duplicate_key_update(**activity_data) + + result = self.execute(conflict) + if result is None: + self.logger.warn(f"put_profile_activity: failed to update! user_id: {user_id}") + return None + return result.lastrowid + + def get_profile_activity(self, user_id: int, kind: int = None) -> Optional[Row]: + sql = activity.select( + and_( + activity.c.user == user_id, + (activity.c.kind == kind) if kind is not None else True, + ) + ) + + result = self.execute(sql) + if result is None:return None + return result.fetchone() diff --git a/titles/mai2/schema/score.py b/titles/mai2/schema/score.py new file mode 100644 index 0000000..0ef6da4 --- /dev/null +++ b/titles/mai2/schema/score.py @@ -0,0 +1,226 @@ +from typing import Dict, List, Optional +from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_ +from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, BigInteger +from sqlalchemy.schema import ForeignKey +from sqlalchemy.sql import func, select +from sqlalchemy.engine import Row +from sqlalchemy.dialects.mysql import insert + +from core.data.schema import BaseData, metadata + +best_score = Table( + "mai2_score_best", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("musicId", Integer), + Column("level", Integer), + Column("playCount", Integer), + Column("achievement", Integer), + Column("comboStatus", Integer), + Column("syncStatus", Integer), + Column("deluxscoreMax", Integer), + Column("scoreRank", Integer), + UniqueConstraint("user", "musicId", "level", name="mai2_score_best_uk"), + mysql_charset='utf8mb4' +) + +playlog = Table( + "mai2_playlog", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("userId", BigInteger), + Column("orderId", Integer), + Column("playlogId", BigInteger), + Column("version", Integer), + Column("placeId", Integer), + Column("placeName", String(255)), + Column("loginDate", BigInteger), + Column("playDate", String(255)), + Column("userPlayDate", String(255)), + Column("type", Integer), + Column("musicId", Integer), + Column("level", Integer), + Column("trackNo", Integer), + Column("vsMode", Integer), + Column("vsUserName", String(255)), + Column("vsStatus", Integer), + Column("vsUserRating", Integer), + Column("vsUserAchievement", Integer), + Column("vsUserGradeRank", Integer), + Column("vsRank", Integer), + Column("playerNum", Integer), + Column("playedUserId1", BigInteger), + Column("playedUserName1", String(255)), + Column("playedMusicLevel1", Integer), + Column("playedUserId2", BigInteger), + Column("playedUserName2", String(255)), + Column("playedMusicLevel2", Integer), + Column("playedUserId3", BigInteger), + Column("playedUserName3", String(255)), + Column("playedMusicLevel3", Integer), + Column("characterId1", Integer), + Column("characterLevel1", Integer), + Column("characterAwakening1", Integer), + Column("characterId2", Integer), + Column("characterLevel2", Integer), + Column("characterAwakening2", Integer), + Column("characterId3", Integer), + Column("characterLevel3", Integer), + Column("characterAwakening3", Integer), + Column("characterId4", Integer), + Column("characterLevel4", Integer), + Column("characterAwakening4", Integer), + Column("characterId5", Integer), + Column("characterLevel5", Integer), + Column("characterAwakening5", Integer), + Column("achievement", Integer), + Column("deluxscore", Integer), + Column("scoreRank", Integer), + Column("maxCombo", Integer), + Column("totalCombo", Integer), + Column("maxSync", Integer), + Column("totalSync", Integer), + Column("tapCriticalPerfect", Integer), + Column("tapPerfect", Integer), + Column("tapGreat", Integer), + Column("tapGood", Integer), + Column("tapMiss", Integer), + Column("holdCriticalPerfect", Integer), + Column("holdPerfect", Integer), + Column("holdGreat", Integer), + Column("holdGood", Integer), + Column("holdMiss", Integer), + Column("slideCriticalPerfect", Integer), + Column("slidePerfect", Integer), + Column("slideGreat", Integer), + Column("slideGood", Integer), + Column("slideMiss", Integer), + Column("touchCriticalPerfect", Integer), + Column("touchPerfect", Integer), + Column("touchGreat", Integer), + Column("touchGood", Integer), + Column("touchMiss", Integer), + Column("breakCriticalPerfect", Integer), + Column("breakPerfect", Integer), + Column("breakGreat", Integer), + Column("breakGood", Integer), + Column("breakMiss", Integer), + Column("isTap", Boolean), + Column("isHold", Boolean), + Column("isSlide", Boolean), + Column("isTouch", Boolean), + Column("isBreak", Boolean), + Column("isCriticalDisp", Boolean), + Column("isFastLateDisp", Boolean), + Column("fastCount", Integer), + Column("lateCount", Integer), + Column("isAchieveNewRecord", Boolean), + Column("isDeluxscoreNewRecord", Boolean), + Column("comboStatus", Integer), + Column("syncStatus", Integer), + Column("isClear", Boolean), + Column("beforeRating", Integer), + Column("afterRating", Integer), + Column("beforeGrade", Integer), + Column("afterGrade", Integer), + Column("afterGradeRank", Integer), + Column("beforeDeluxRating", Integer), + Column("afterDeluxRating", Integer), + Column("isPlayTutorial", Boolean), + Column("isEventMode", Boolean), + Column("isFreedomMode", Boolean), + Column("playMode", Integer), + Column("isNewFree", Boolean), + Column("extNum1", Integer), + Column("extNum2", Integer), + mysql_charset='utf8mb4' +) + +course = Table( + "mai2_score_course", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("courseId", Integer), + Column("isLastClear", Boolean), + Column("totalRestlife", Integer), + Column("totalAchievement", Integer), + Column("totalDeluxscore", Integer), + Column("playCount", Integer), + Column("clearDate", String(25)), + Column("lastPlayDate", String(25)), + Column("bestAchievement", Integer), + Column("bestAchievementDate", String(25)), + Column("bestDeluxscore", Integer), + Column("bestDeluxscoreDate", String(25)), + UniqueConstraint("user", "courseId", name="mai2_score_best_uk"), + mysql_charset='utf8mb4' +) + +class Mai2ScoreData(BaseData): + def put_best_score(self, user_id: int, score_data: Dict) -> Optional[int]: + sql = insert(best_score).values(**score_data) + + conflict = sql.on_duplicate_key_update(**score_data) + + result = self.execute(conflict) + if result is None: + self.logger.error(f"put_best_score: Failed to insert best score! user_id {user_id}") + return None + return result.lastrowid + + def get_best_scores(self, user_id: int, song_id: int = None) -> Optional[List[Row]]: + sql = best_score.select( + and_( + best_score.c.user == user_id, + (best_score.c.song_id == song_id) if song_id is not None else True + ) + ) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def get_best_score(self, user_id: int, song_id: int, chart_id: int) -> Optional[Row]: + sql = best_score.select( + and_( + best_score.c.user == user_id, + best_score.c.song_id == song_id, + best_score.c.chart_id == chart_id + ) + ) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() + + def put_playlog(self, user_id: int, playlog_data: Dict) -> Optional[int]: + sql = insert(playlog).values(**playlog_data) + + conflict = sql.on_duplicate_key_update(**playlog_data) + + result = self.execute(conflict) + if result is None: + self.logger.error(f"put_playlog: Failed to insert! user_id {user_id}") + return None + return result.lastrowid + + def put_course(self, user_id: int, course_data: Dict) -> Optional[int]: + sql = insert(course).values(**course_data) + + conflict = sql.on_duplicate_key_update(**course_data) + + result = self.execute(conflict) + if result is None: + self.logger.error(f"put_course: Failed to insert! user_id {user_id}") + return None + return result.lastrowid + + def get_courses(self, user_id: int) -> Optional[List[Row]]: + sql = course.select(best_score.c.user == user_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() diff --git a/titles/mai2/schema/static.py b/titles/mai2/schema/static.py new file mode 100644 index 0000000..733e2ef --- /dev/null +++ b/titles/mai2/schema/static.py @@ -0,0 +1,178 @@ +from core.data.schema.base import BaseData, metadata + +from typing import Optional, Dict, List +from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_ +from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, Float +from sqlalchemy.schema import ForeignKey +from sqlalchemy.sql import func, select +from sqlalchemy.engine import Row +from sqlalchemy.dialects.mysql import insert + +event = Table( + "mai2_static_event", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("version", Integer,nullable=False), + Column("eventId", Integer), + Column("type", Integer), + Column("name", String(255)), + Column("enabled", Boolean, server_default="1"), + UniqueConstraint("version", "eventId", "type", name="mai2_static_event_uk"), + mysql_charset='utf8mb4' +) + +music = Table( + "mai2_static_music", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("version", Integer,nullable=False), + Column("songId", Integer), + Column("chartId", Integer), + Column("title", String(255)), + Column("artist", String(255)), + Column("genre", String(255)), + Column("bpm", Integer), + Column("addedVersion", String(255)), + Column("difficulty", Float), + Column("noteDesigner", String(255)), + UniqueConstraint("songId", "chartId", "version", name="mai2_static_music_uk"), + mysql_charset='utf8mb4' +) + +ticket = Table( + "mai2_static_ticket", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("version", Integer,nullable=False), + Column("ticketId", Integer), + Column("kind", Integer), + Column("name", String(255)), + Column("price", Integer, server_default="1"), + Column("enabled", Boolean, server_default="1"), + UniqueConstraint("version","ticketId", name="mai2_static_ticket_uk"), + mysql_charset='utf8mb4' +) + +class Mai2StaticData(BaseData): + def put_game_event(self, version: int, type: int, event_id: int, name: str) -> Optional[int]: + sql = insert(event).values( + version = version, + type = type, + eventId = event_id, + name = name, + ) + + conflict = sql.on_duplicate_key_update( + eventId = event_id + ) + + result = self.execute(conflict) + if result is None: + self.logger.warning(f"put_game_event: Failed to insert event! event_id {event_id} type {type} name {name}") + return result.lastrowid + + def get_game_events(self, version: int) -> Optional[List[Row]]: + sql = event.select(event.c.version == version) + + result = self.execute(sql) + if result is None: + return None + return result.fetchall() + + def get_enabled_events(self, version: int) -> Optional[List[Row]]: + sql = select(event).where(and_( + event.c.version == version, + event.c.enabled == True + )) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def toggle_game_events(self, version: int, event_id: int, toggle: bool) -> Optional[List]: + sql = event.update(and_(event.c.version == version, event.c.event_id == event_id)).values( + enabled = int(toggle) + ) + + result = self.execute(sql) + if result is None: + self.logger.warning(f"toggle_game_events: Failed to update event! event_id {event_id} toggle {toggle}") + return result.last_updated_params() + + def put_game_music(self, version: int, song_id: int, chart_id: int, title: str, artist: str, + genre: str, bpm: str, added_version: str, difficulty: float, note_designer: str) -> None: + sql = insert(music).values( + version = version, + songId = song_id, + chartId = chart_id, + title = title, + artist = artist, + genre = genre, + bpm = bpm, + addedVersion = added_version, + difficulty = difficulty, + noteDesigner = note_designer, + ) + + conflict = sql.on_duplicate_key_update( + title = title, + artist = artist, + genre = genre, + bpm = bpm, + addedVersion = added_version, + difficulty = difficulty, + noteDesigner = note_designer, + ) + + result = self.execute(conflict) + if result is None: + self.logger.warn(f"Failed to insert song {song_id} chart {chart_id}") + return None + return result.lastrowid + + def put_game_ticket(self, version: int, ticket_id: int, ticket_type: int, ticket_price: int, name: str) -> Optional[int]: + sql = insert(ticket).values( + version = version, + ticketId = ticket_id, + kind = ticket_type, + price = ticket_price, + name = name + ) + + conflict = sql.on_duplicate_key_update( + price = ticket_price + ) + + result = self.execute(conflict) + if result is None: + self.logger.warn(f"Failed to insert charge {ticket_id} type {ticket_type}") + return None + return result.lastrowid + + def get_enabled_tickets(self, version: int, kind: int = None) -> Optional[List[Row]]: + if kind is not None: + sql = select(ticket).where(and_( + ticket.c.version == version, + ticket.c.enabled == True, + ticket.c.kind == kind + )) + else: + sql = select(ticket).where(and_( + ticket.c.version == version, + ticket.c.enabled == True + )) + + result = self.execute(sql) + if result is None:return None + return result.fetchall() + + def get_music_chart(self, version: int, song_id: int, chart_id: int) -> Optional[List[Row]]: + sql = select(music).where(and_( + music.c.version == version, + music.c.songId == song_id, + music.c.chartId == chart_id + )) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() diff --git a/titles/mai2/splash.py b/titles/mai2/splash.py new file mode 100644 index 0000000..690645b --- /dev/null +++ b/titles/mai2/splash.py @@ -0,0 +1,14 @@ +from typing import Any, List, Dict +from datetime import datetime, timedelta +import pytz +import json + +from core.config import CoreConfig +from titles.mai2.base import Mai2Base +from titles.mai2.config import Mai2Config +from titles.mai2.const import Mai2Constants + +class Mai2Splash(Mai2Base): + def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None: + super().__init__(cfg, game_cfg) + self.version = Mai2Constants.VER_MAIMAI_DX_SPLASH \ No newline at end of file diff --git a/titles/mai2/splashplus.py b/titles/mai2/splashplus.py new file mode 100644 index 0000000..eb6f940 --- /dev/null +++ b/titles/mai2/splashplus.py @@ -0,0 +1,14 @@ +from typing import Any, List, Dict +from datetime import datetime, timedelta +import pytz +import json + +from core.config import CoreConfig +from titles.mai2.base import Mai2Base +from titles.mai2.config import Mai2Config +from titles.mai2.const import Mai2Constants + +class Mai2SplashPlus(Mai2Base): + def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None: + super().__init__(cfg, game_cfg) + self.version = Mai2Constants.VER_MAIMAI_DX_SPLASH_PLUS \ No newline at end of file diff --git a/titles/mai2/universe.py b/titles/mai2/universe.py new file mode 100644 index 0000000..be6472c --- /dev/null +++ b/titles/mai2/universe.py @@ -0,0 +1,14 @@ +from typing import Any, List, Dict +from datetime import datetime, timedelta +import pytz +import json + +from core.config import CoreConfig +from titles.mai2.base import Mai2Base +from titles.mai2.const import Mai2Constants +from titles.mai2.config import Mai2Config + +class Mai2Universe(Mai2Base): + def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None: + super().__init__(cfg, game_cfg) + self.version = Mai2Constants.VER_MAIMAI_DX_UNIVERSE \ No newline at end of file diff --git a/titles/mai2/universeplus.py b/titles/mai2/universeplus.py new file mode 100644 index 0000000..795206e --- /dev/null +++ b/titles/mai2/universeplus.py @@ -0,0 +1,14 @@ +from typing import Any, List, Dict +from datetime import datetime, timedelta +import pytz +import json + +from core.config import CoreConfig +from titles.mai2.base import Mai2Base +from titles.mai2.const import Mai2Constants +from titles.mai2.config import Mai2Config + +class Mai2UniversePlus(Mai2Base): + def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None: + super().__init__(cfg, game_cfg) + self.version = Mai2Constants.VER_MAIMAI_DX_UNIVERSE_PLUS \ No newline at end of file diff --git a/titles/ongeki/__init__.py b/titles/ongeki/__init__.py new file mode 100644 index 0000000..26e107c --- /dev/null +++ b/titles/ongeki/__init__.py @@ -0,0 +1,18 @@ +from titles.ongeki.index import OngekiServlet +from titles.ongeki.const import OngekiConstants +from titles.ongeki.database import OngekiData +from titles.ongeki.read import OngekiReader + +index = OngekiServlet +database = OngekiData +reader = OngekiReader + +use_default_title = True +include_protocol = True +title_secure = False +game_codes = [OngekiConstants.GAME_CODE] +trailing_slash = True +use_default_host = False +host = "" + +current_schema_version = 2 \ No newline at end of file diff --git a/titles/ongeki/base.py b/titles/ongeki/base.py new file mode 100644 index 0000000..8778645 --- /dev/null +++ b/titles/ongeki/base.py @@ -0,0 +1,934 @@ +from datetime import date, datetime, timedelta +from typing import Any, Dict, List +import json +import logging +from enum import Enum + +from core.config import CoreConfig +from core.data.cache import cached +from titles.ongeki.const import OngekiConstants +from titles.ongeki.config import OngekiConfig +from titles.ongeki.database import OngekiData +from titles.ongeki.config import OngekiConfig + +class OngekiBattleGrade(Enum): + FAILED = 0 + DRAW = 1 + USUALLY = 2 + GOOD = 3 + GREAT = 4 + EXCELLENT = 5 + UNBELIEVABLE_GOLD = 6 + UNBELIEVABLE_RAINBOW = 7 + +class OngekiBattlePointGrade(Enum): + FRESHMAN = 0 + KYU10 = 1 + KYU9 = 2 + KYU8 = 3 + KYU7 = 4 + KYU6 = 5 + KYU5 = 6 + KYU4 = 7 + KYU3 = 8 + KYU2 = 9 + KYU1 = 10 + DAN1 = 11 + DAN2 = 12 + DAN3 = 13 + DAN4 = 14 + DAN5 = 15 + DAN6 = 16 + DAN7 = 17 + DAN8 = 18 + DAN9 = 19 + DAN10 = 20 + SODEN = 21 + +class OngekiTechnicalGrade(Enum): + D = 0 + C = 1 + B = 2 + BB = 3 + BBB = 4 + A = 5 + AA = 6 + AAA = 7 + S = 8 + SS = 9 + SSS = 10 + SSSp = 11 + +class OngekiDifficulty(Enum): + BASIC = 0 + ADVANCED = 1 + EXPERT = 2 + MASTER = 3 + LUNATIC = 10 + +class OngekiGPLogKind(Enum): + NONE = 0 + BUY1_START = 1 + BUY2_START = 2 + BUY3_START = 3 + BUY1_ADD = 4 + BUY2_ADD = 5 + BUY3_ADD = 6 + FIRST_PLAY = 7 + COMPENSATION = 8 + + PAY_PLAY = 11 + PAY_TIME = 12 + PAY_MAS_UNLOCK = 13 + PAY_MONEY = 14 + +class OngekiBase(): + + def __init__(self, core_cfg: CoreConfig, game_cfg: OngekiConfig) -> None: + self.core_cfg = core_cfg + self.game_cfg = game_cfg + self.data = OngekiData(core_cfg) + self.date_time_format = "%Y-%m-%d %H:%M:%S" + self.date_time_format_ext = "%Y-%m-%d %H:%M:%S.%f" # needs to be lopped off at [:-5] + self.date_time_format_short = "%Y-%m-%d" + self.logger = logging.getLogger("ongeki") + self.game = OngekiConstants.GAME_CODE + self.version = OngekiConstants.VER_ONGEKI + + def handle_get_game_setting_api_request(self, data: Dict) -> Dict: + reboot_start = date.strftime(datetime.now() + timedelta(hours=3), self.date_time_format) + reboot_end = date.strftime(datetime.now() + timedelta(hours=4), self.date_time_format) + return { + "gameSetting": { + "dataVersion": "1.00.00", + "onlineDataVersion": "1.00.00", + "isMaintenance": "false", + "requestInterval": 10, + "rebootStartTime": reboot_start, + "rebootEndTime": reboot_end, + "isBackgroundDistribute": "false", + "maxCountCharacter": 50, + "maxCountCard": 300, + "maxCountItem": 300, + "maxCountMusic": 50, + "maxCountMusicItem": 300, + "macCountRivalMusic": 300, + }, + "isDumpUpload": "false", + "isAou": "true", + } + + def handle_get_game_idlist_api_request(self, data: Dict) -> Dict: + """ + Gets lists of song IDs, either disabled songs or recomended songs depending on type? + """ + # type - int + # id - int + return {"type": data["type"], "length": 0, "gameIdlistList": []} + + def handle_get_game_ranking_api_request(self, data: Dict) -> Dict: + return {"length": 0, "gameRankingList": []} + + def handle_get_game_point_api_request(self, data: Dict) -> Dict: + """ + Sets the GP ammount for A and B sets for 1 - 3 crdits + """ + return {"length":6,"gamePointList":[ + {"type":0,"cost":100,"startDate":"2000-01-01 05:00:00.0","endDate":"2099-01-01 05:00:00.0"}, + {"type":1,"cost":200,"startDate":"2000-01-01 05:00:00.0","endDate":"2099-01-01 05:00:00.0"}, + {"type":2,"cost":300,"startDate":"2000-01-01 05:00:00.0","endDate":"2099-01-01 05:00:00.0"}, + {"type":3,"cost":120,"startDate":"2000-01-01 05:00:00.0","endDate":"2099-01-01 05:00:00.0"}, + {"type":4,"cost":240,"startDate":"2000-01-01 05:00:00.0","endDate":"2099-01-01 05:00:00.0"}, + {"type":5,"cost":360,"startDate":"2000-01-01 05:00:00.0","endDate":"2099-01-01 05:00:00.0"} + ]} + + def handle_game_login_api_request(self, data: Dict) -> Dict: + return {"returnCode": 1, "apiName": "gameLogin"} + + def handle_game_logout_api_request(self, data: Dict) -> Dict: + return {"returnCode": 1, "apiName": "gameLogout"} + + def handle_extend_lock_time_api_request(self, data: Dict) -> Dict: + return {"returnCode": 1, "apiName": "ExtendLockTimeApi"} + + def handle_get_game_reward_api_request(self, data: Dict) -> Dict: + # TODO: reward list + return {"length": 0, "gameRewardList": []} + + def handle_get_game_present_api_request(self, data: Dict) -> Dict: + return {"length": 0, "gamePresentList": []} + + def handle_get_game_message_api_request(self, data: Dict) -> Dict: + return {"length": 0, "gameMessageList": []} + + def handle_get_game_sale_api_request(self, data: Dict) -> Dict: + return {"length": 0, "gameSaleList": []} + + def handle_get_game_tech_music_api_request(self, data: Dict) -> Dict: + return {"length": 0, "gameTechMusicList": []} + + def handle_upsert_client_setting_api_request(self, data: Dict) -> Dict: + return {"returnCode": 1, "apiName": "UpsertClientSettingApi"} + + def handle_upsert_client_testmode_api_request(self, data: Dict) -> Dict: + return {"returnCode": 1, "apiName": "UpsertClientTestmodeApi"} + + def handle_upsert_client_bookkeeping_api_request(self, data: Dict) -> Dict: + return {"returnCode": 1, "apiName": "upsertClientBookkeeping"} + + def handle_upsert_client_develop_api_request(self, data: Dict) -> Dict: + return {"returnCode": 1, "apiName": "upsertClientDevelop"} + + def handle_upsert_client_error_api_request(self, data: Dict) -> Dict: + return {"returnCode": 1, "apiName": "upsertClientError"} + + def handle_upsert_user_gplog_api_request(self, data: Dict) -> Dict: + user = data["userId"] + if user >= 200000000000000: # Account for guest play + user = None + + self.data.log.put_gp_log(user, data["usedCredit"], data["placeName"], data["userGplog"]["trxnDate"], + data["userGplog"]["placeId"], data["userGplog"]["kind"], data["userGplog"]["pattern"], data["userGplog"]["currentGP"]) + + return {"returnCode": 1, "apiName": "UpsertUserGplogApi"} + + def handle_extend_lock_time_api_request(self, data: Dict) -> Dict: + return {"returnCode": 1, "apiName": "ExtendLockTimeApi"} + + def handle_get_game_event_api_request(self, data: Dict) -> Dict: + evts = self.data.static.get_enabled_events(self.version) + + evt_list = [] + for event in evts: + evt_list.append({ + "type": event["type"], + "id": event["eventId"], + "startDate": "2017-12-05 07:00:00.0", + "endDate": "2099-12-31 00:00:00.0" + }) + + return {"type": data["type"], "length": len(evt_list), "gameEventList": evt_list} + + def handle_get_game_id_list_api_request(self, data: Dict) -> Dict: + game_idlist: list[str, Any] = [] #1 to 230 & 8000 to 8050 + + if data["type"] == 1: + for i in range(1,231): + game_idlist.append({"type": 1, "id": i}) + return {"type": data["type"], "length": len(game_idlist), "gameIdlistList": game_idlist} + elif data["type"] == 2: + for i in range(8000,8051): + game_idlist.append({"type": 2, "id": i}) + return {"type": data["type"], "length": len(game_idlist), "gameIdlistList": game_idlist} + + def handle_get_user_region_api_request(self, data: Dict) -> Dict: + return {"userId": data["userId"], "length": 0, "userRegionList": []} + + def handle_get_user_preview_api_request(self, data: Dict) -> Dict: + profile = self.data.profile.get_profile_preview(data["userId"], self.version) + + if profile is None: + return { + "userId": data["userId"], + "isLogin": False, + "lastLoginDate": "0000-00-00 00:00:00", + "userName": "", + "reincarnationNum": 0, + "level": 0, + "exp": 0, + "playerRating": 0, + "lastGameId": "", + "lastRomVersion": "", + "lastDataVersion": "", + "lastPlayDate": "", + "nameplateId": 0, + "trophyId": 0, + "cardId": 0, + "dispPlayerLv": 0, + "dispRating": 0, + "dispBP": 0, + "headphone": 0, + "banStatus": 0, + "isWarningConfirmed": True, + } + + return { + "userId": data["userId"], + "isLogin": False, + "lastLoginDate": profile["lastPlayDate"], + "userName": profile["userName"], + "reincarnationNum": profile["reincarnationNum"], + "level": profile["level"], + "exp": profile["exp"], + "playerRating": profile["playerRating"], + "lastGameId": profile["lastGameId"], + "lastRomVersion": profile["lastRomVersion"], + "lastDataVersion": profile["lastDataVersion"], + "lastPlayDate": profile["lastPlayDate"], + "nameplateId": profile["nameplateId"], + "trophyId": profile["trophyId"], + "cardId": profile["cardId"], + "dispPlayerLv": profile["dispPlayerLv"], + "dispRating": profile["dispRating"], + "dispBP": profile["dispBP"], + "headphone": profile["headphone"], + "banStatus": profile["banStatus"], + "isWarningConfirmed": True, + } + + def handle_get_user_tech_count_api_request(self, data: Dict) -> Dict: + """ + Gets the number of AB and ABPs a player has per-difficulty (7, 7+, 8, etc) + The game sends this in upsert so we don't have to calculate it all out thankfully + """ + utcl = self.data.score.get_tech_count(data["userId"]) + userTechCountList = [] + + for tc in utcl: + tc.pop("id") + tc.pop("user") + userTechCountList.append(tc) + + return { + "userId": data["userId"], + "length": len(userTechCountList), + "userTechCountList": userTechCountList, + } + + def handle_get_user_tech_event_api_request(self, data: Dict) -> Dict: + user_tech_event_list = self.data.item.get_tech_event(data["userId"]) + if user_tech_event_list is None: return {} + + tech_evt = [] + for evt in user_tech_event_list: + tmp = evt._asdict() + tmp.pop("id") + tmp.pop("user") + tech_evt.append(tmp) + + return { + "userId": data["userId"], + "length": len(tech_evt), + "userTechEventList": tech_evt, + } + + def handle_get_user_tech_event_ranking_api_request(self, data: Dict) -> Dict: + #user_event_ranking_list = self.data.item.get_tech_event_ranking(data["userId"]) + #if user_event_ranking_list is None: return {} + + evt_ranking = [] + #for evt in user_event_ranking_list: + # tmp = evt._asdict() + # tmp.pop("id") + # tmp.pop("user") + # evt_ranking.append(tmp) + + return { + "userId": data["userId"], + "length": len(evt_ranking), + "userTechEventRankingList": evt_ranking, + } + + def handle_get_user_kop_api_request(self, data: Dict) -> Dict: + kop_list = self.data.profile.get_kop(data["userId"]) + if kop_list is None: return {} + + for kop in kop_list: + kop.pop("user") + kop.pop("id") + + return { + "userId": data["userId"], + "length": len(kop_list), + "userKopList": kop_list, + } + + def handle_get_user_music_api_request(self, data: Dict) -> Dict: + song_list = self.util_generate_music_list(data["userId"]) + max_ct = data["maxCount"] + next_idx = data["nextIndex"] + start_idx = next_idx + end_idx = max_ct + start_idx + + if len(song_list[start_idx:]) > max_ct: + next_idx += max_ct + + else: + next_idx = -1 + + return { + "userId": data["userId"], + "length": len(song_list[start_idx:end_idx]), + "nextIndex": next_idx, + "userMusicList": song_list[start_idx:end_idx] + } + + def handle_get_user_item_api_request(self, data: Dict) -> Dict: + kind = data["nextIndex"] / 10000000000 + p = self.data.item.get_items(data["userId"], kind) + + if p is None: + return {"userId": data["userId"], "nextIndex": -1, "itemKind": kind, "userItemList": []} + + items: list[Dict[str, Any]] = [] + for i in range(data["nextIndex"] % 10000000000, len(p)): + if len(items) > data["maxCount"]: + break + tmp = p[i]._asdict() + tmp.pop("user") + tmp.pop("id") + items.append(tmp) + + xout = kind * 10000000000 + (data["nextIndex"] % 10000000000) + len(items) + + if len(items) < data["maxCount"] or data["maxCount"] == 0: nextIndex = 0 + else: nextIndex = xout + + return {"userId": data["userId"], "nextIndex": int(nextIndex), "itemKind": int(kind), "length": len(items), "userItemList": items} + + def handle_get_user_option_api_request(self, data: Dict) -> Dict: + o = self.data.profile.get_profile_options(data["userId"]) + if o is None: return {} + + # get the dict representation of the row so we can modify values + user_opts = o._asdict() + + # remove the values the game doesn't want + user_opts.pop("id") + user_opts.pop("user") + + return {"userId": data["userId"], "userOption": user_opts} + + def handle_get_user_data_api_request(self, data: Dict) -> Dict: + p = self.data.profile.get_profile_data(data["userId"], self.version) + if p is None: return {} + + cards = self.data.card.get_user_cards(data["userId"]) + if cards is None or len(cards) == 0: + # This should never happen + self.logger.error(f"handle_get_user_data_api_request: Internal error - No cards found for user id {data['userId']}") + return {} + + # get the dict representation of the row so we can modify values + user_data = p._asdict() + + # remove the values the game doesn't want + user_data.pop("id") + user_data.pop("user") + user_data.pop("version") + + # TODO: replace datetime objects with strings + + # add access code that we don't store + user_data["accessCode"] = cards[0]["access_code"] + + return {"userId": data["userId"], "userData":user_data} + + def handle_get_user_event_ranking_api_request(self, data: Dict) -> Dict: + #user_event_ranking_list = self.data.item.get_event_ranking(data["userId"]) + #if user_event_ranking_list is None: return {} + + evt_ranking = [] + #for evt in user_event_ranking_list: + # tmp = evt._asdict() + # tmp.pop("id") + # tmp.pop("user") + # evt_ranking.append(tmp) + + return { + "userId": data["userId"], + "length": len(evt_ranking), + "userEventRankingList": evt_ranking, + } + + def handle_get_user_login_bonus_api_request(self, data: Dict) -> Dict: + user_login_bonus_list = self.data.item.get_login_bonuses(data["userId"]) + if user_login_bonus_list is None: return {} + + login_bonuses = [] + for scenerio in user_login_bonus_list: + tmp = scenerio._asdict() + tmp.pop("id") + tmp.pop("user") + login_bonuses.append(tmp) + + return { + "userId": data["userId"], + "length": len(login_bonuses), + "userLoginBonusList": login_bonuses + } + + def handle_get_user_bp_base_request(self, data: Dict) -> Dict: + p = self.data.profile.get_profile(self.game, self.version, user_id = data["userId"]) + if p is None: return {} + profile = json.loads(p["data"]) + return { + "userId": data["userId"], + "length": len(profile["userBpBaseList"]), + "userBpBaseList": profile["userBpBaseList"], + } + + def handle_get_user_recent_rating_api_request(self, data: Dict) -> Dict: + recent_rating = self.data.profile.get_profile_recent_rating(data["userId"]) + if recent_rating is None: return {} + + userRecentRatingList = recent_rating["recentRating"] + + return { + "userId": data["userId"], + "length": len(userRecentRatingList), + "userRecentRatingList": userRecentRatingList, + } + + def handle_get_user_activity_api_request(self, data: Dict) -> Dict: + activity = self.data.profile.get_profile_activity(data["userId"], data["kind"]) + if activity is None: return {} + + user_activity = [] + + for act in activity: + user_activity.append({ + "kind": act["kind"], + "id": act["activityId"], + "sortNumber": act["sortNumber"], + "param1": act["param1"], + "param2": act["param2"], + "param3": act["param3"], + "param4": act["param4"], + }) + + return { + "userId": data["userId"], + "length": len(user_activity), + "kind": data["kind"], + "userActivityList": user_activity + } + + def handle_get_user_story_api_request(self, data: Dict) -> Dict: + user_stories = self.data.item.get_stories(data["userId"]) + if user_stories is None: return {} + + story_list = [] + for story in user_stories: + tmp = story._asdict() + tmp.pop("id") + tmp.pop("user") + story_list.append(tmp) + + return { + "userId": data["userId"], + "length": len(story_list), + "userStoryList": story_list + } + + def handle_get_user_chapter_api_request(self, data: Dict) -> Dict: + user_chapters = self.data.item.get_chapters(data["userId"]) + if user_chapters is None: return {} + + chapter_list = [] + for chapter in user_chapters: + tmp = chapter._asdict() + tmp.pop("id") + tmp.pop("user") + chapter_list.append(tmp) + + return { + "userId": data["userId"], + "length": len(chapter_list), + "userChapterList": chapter_list + } + + def handle_get_user_training_room_by_key_api_request(self, data: Dict) -> Dict: + return { + "userId": data["userId"], + "length": 0, + "userTrainingRoomList": [], + } + + def handle_get_user_character_api_request(self, data: Dict) -> Dict: + user_characters = self.data.item.get_characters(data["userId"]) + if user_characters is None: return {} + + character_list = [] + for character in user_characters: + tmp = character._asdict() + tmp.pop("id") + tmp.pop("user") + character_list.append(tmp) + + return { + "userId": data["userId"], + "length": len(character_list), + "userCharacterList": character_list + } + + def handle_get_user_card_api_request(self, data: Dict) -> Dict: + user_cards = self.data.item.get_cards(data["userId"]) + if user_cards is None: return {} + + card_list = [] + for card in user_cards: + tmp = card._asdict() + tmp.pop("id") + tmp.pop("user") + card_list.append(tmp) + + return { + "userId": data["userId"], + "length": len(card_list), + "userCardList": card_list + } + + def handle_get_user_deck_by_key_api_request(self, data: Dict) -> Dict: + # Auth key doesn't matter, it just wants all the decks + decks = self.data.item.get_decks(data["userId"]) + if decks is None: return {} + + deck_list = [] + for deck in decks: + tmp = deck._asdict() + tmp.pop("user") + tmp.pop("id") + deck_list.append(tmp) + + return { + "userId": data["userId"], + "length": len(deck_list), + "userDeckList": deck_list, + } + + def handle_get_user_trade_item_api_request(self, data: Dict) -> Dict: + user_trade_items = self.data.item.get_trade_items(data["userId"]) + if user_trade_items is None: return {} + + trade_item_list = [] + for trade_item in user_trade_items: + tmp = trade_item._asdict() + tmp.pop("id") + tmp.pop("user") + trade_item_list.append(tmp) + + return { + "userId": data["userId"], + "length": len(trade_item_list), + "userTradeItemList": trade_item_list, + } + + def handle_get_user_scenario_api_request(self, data: Dict) -> Dict: + user_scenerio = self.data.item.get_scenerios(data["userId"]) + if user_scenerio is None: return {} + + scenerio_list = [] + for scenerio in user_scenerio: + tmp = scenerio._asdict() + tmp.pop("id") + tmp.pop("user") + scenerio_list.append(tmp) + + return { + "userId": data["userId"], + "length": len(scenerio_list), + "userScenarioList": scenerio_list, + } + + def handle_get_user_ratinglog_api_request(self, data: Dict) -> Dict: + rating_log = self.data.profile.get_profile_rating_log(data["userId"]) + if rating_log is None: return {} + + userRatinglogList = [] + for rating in rating_log: + tmp = rating._asdict() + tmp.pop("id") + tmp.pop("user") + userRatinglogList.append(tmp) + + return { + "userId": data["userId"], + "length": len(userRatinglogList), + "userRatinglogList": userRatinglogList, + } + + def handle_get_user_mission_point_api_request(self, data: Dict) -> Dict: + user_mission_point_list = self.data.item.get_mission_points(data["userId"]) + if user_mission_point_list is None: return {} + + mission_point_list = [] + for evt_music in user_mission_point_list: + tmp = evt_music._asdict() + tmp.pop("id") + tmp.pop("user") + mission_point_list.append(tmp) + + return { + "userId": data["userId"], + "length": len(mission_point_list), + "userMissionPointList": mission_point_list, + } + + def handle_get_user_event_point_api_request(self, data: Dict) -> Dict: + user_event_point_list = self.data.item.get_event_points(data["userId"]) + if user_event_point_list is None: return {} + + event_point_list = [] + for evt_music in user_event_point_list: + tmp = evt_music._asdict() + tmp.pop("id") + tmp.pop("user") + event_point_list.append(tmp) + + return { + "userId": data["userId"], + "length": len(event_point_list), + "userEventPointList": event_point_list, + } + + def handle_get_user_music_item_api_request(self, data: Dict) -> Dict: + user_music_item_list = self.data.item.get_music_items(data["userId"]) + if user_music_item_list is None: return {} + + music_item_list = [] + for evt_music in user_music_item_list: + tmp = evt_music._asdict() + tmp.pop("id") + tmp.pop("user") + music_item_list.append(tmp) + + return { + "userId": data["userId"], + "length": len(music_item_list), + "userMusicItemList": music_item_list, + } + + def handle_get_user_event_music_api_request(self, data: Dict) -> Dict: + user_evt_music_list = self.data.item.get_event_music(data["userId"]) + if user_evt_music_list is None: return {} + + evt_music_list = [] + for evt_music in user_evt_music_list: + tmp = evt_music._asdict() + tmp.pop("id") + tmp.pop("user") + evt_music_list.append(tmp) + + return { + "userId": data["userId"], + "length": len(evt_music_list), + "userEventMusicList": evt_music_list, + } + + def handle_get_user_boss_api_request(self, data: Dict) -> Dict: + p = self.data.item.get_bosses(data["userId"]) + if p is None: return {} + + boss_list = [] + for boss in p: + tmp = boss._asdict() + tmp.pop("id") + tmp.pop("user") + boss_list.append(tmp) + + return { + "userId": data["userId"], + "length": len(boss_list), + "userBossList": boss_list, + } + + def handle_upsert_user_all_api_request(self, data: Dict) -> Dict: + upsert = data["upsertUserAll"] + user_id = data["userId"] + + # The isNew fields are new as of Red and up. We just won't use them for now. + + if "userData" in upsert and len(upsert["userData"]) > 0: + self.data.profile.put_profile_data(user_id, self.version, upsert["userData"][0]) + + if "userOption" in upsert and len(upsert["userOption"]) > 0: + self.data.profile.put_profile_options(user_id, upsert["userOption"][0]) + + if "userPlaylogList" in upsert: + for playlog in upsert["userPlaylogList"]: + self.data.score.put_playlog(user_id, playlog) + + if "userActivityList" in upsert: + for act in upsert["userActivityList"]: + self.data.profile.put_profile_activity(user_id, act["kind"], act["id"], act["sortNumber"], act["param1"], + act["param2"], act["param3"], act["param4"]) + + if "userRecentRatingList" in upsert: + self.data.profile.put_profile_recent_rating(user_id, upsert["userRecentRatingList"]) + + if "userBpBaseList" in upsert: + self.data.profile.put_profile_bp_list(user_id, upsert["userBpBaseList"]) + + if "userMusicDetailList" in upsert: + for x in upsert["userMusicDetailList"]: + self.data.score.put_best_score(user_id, x) + + if "userCharacterList" in upsert: + for x in upsert["userCharacterList"]: + self.data.item.put_character(user_id, x) + + if "userCardList" in upsert: + for x in upsert["userCardList"]: + self.data.item.put_card(user_id, x) + + if "userDeckList" in upsert: + for x in upsert["userDeckList"]: + self.data.item.put_deck(user_id, x) + + if "userTrainingRoomList" in upsert: + for x in upsert["userTrainingRoomList"]: + self.data.profile.put_training_room(user_id, x) + + if "userStoryList" in upsert: + for x in upsert["userStoryList"]: + self.data.item.put_story(user_id, x) + + if "userChapterList" in upsert: + for x in upsert["userChapterList"]: + self.data.item.put_chapter(user_id, x) + + if "userItemList" in upsert: + for x in upsert["userItemList"]: + self.data.item.put_item(user_id, x) + + if "userMusicItemList" in upsert: + for x in upsert["userMusicItemList"]: + self.data.item.put_music_item(user_id, x) + + if "userLoginBonusList" in upsert: + for x in upsert["userLoginBonusList"]: + self.data.item.put_login_bonus(user_id, x) + + if "userEventPointList" in upsert: + for x in upsert["userEventPointList"]: + self.data.item.put_event_point(user_id, x) + + if "userMissionPointList" in upsert: + for x in upsert["userMissionPointList"]: + self.data.item.put_mission_point(user_id, x) + + if "userRatinglogList" in upsert: + for x in upsert["userRatinglogList"]: + self.data.profile.put_profile_rating_log(user_id, x["dataVersion"], x["highestRating"]) + + if "userBossList" in upsert: + for x in upsert["userBossList"]: + self.data.item.put_boss(user_id, x) + + if "userTechCountList" in upsert: + for x in upsert["userTechCountList"]: + self.data.score.put_tech_count(user_id, x) + + if "userScenerioList" in upsert: + for x in upsert["userScenerioList"]: + self.data.item.put_scenerio(user_id, x) + + if "userTradeItemList" in upsert: + for x in upsert["userTradeItemList"]: + self.data.item.put_trade_item(user_id, x) + + if "userEventMusicList" in upsert: + for x in upsert["userEventMusicList"]: + self.data.item.put_event_music(user_id, x) + + if "userTechEventList" in upsert: + for x in upsert["userTechEventList"]: + self.data.item.put_tech_event(user_id, x) + + if "userKopList" in upsert: + for x in upsert["userKopList"]: + self.data.profile.put_kop(user_id, x) + + return {'returnCode': 1, 'apiName': 'upsertUserAll'} + + def handle_get_user_rival_api_request(self, data: Dict) -> Dict: + """ + Added in Bright + """ + rival_list = self.data.profile.get_rivals(data["userId"]) + if rival_list is None or len(rival_list) < 1: + return { + "userId": data["userId"], + "length": 0, + "userRivalList": [], + } + + return { + "userId": data["userId"], + "length": len(rival_list), + "userRivalList": rival_list._asdict(), + } + + def handle_get_user_rival_data_api_reqiest(self, data:Dict) -> Dict: + """ + Added in Bright + """ + rivals = [] + + for rival in data["userRivalList"]: + name = self.data.profile.get_profile_name(rival["rivalUserId"], self.version) + if name is None: + continue + + rivals.append({ + "rivalUserId": rival["rival"], + "rivalUserName": name + }) + + return { + "userId": data["userId"], + "length": len(rivals), + "userRivalDataList": rivals, + } + + def handle_get_user_rival_music_api_request(self, data: Dict) -> Dict: + """ + Added in Bright + """ + rival_id = data["rivalUserId"] + next_idx = data["nextIndex"] + max_ct = data["maxCount"] + music = self.handle_get_user_music_api_request({ + "userId": rival_id, + "nextIndex": next_idx, + "maxCount": max_ct + }) + + for song in music["userMusicList"]: + song["userRivalMusicDetailList"] = song["userMusicDetailList"] + song.pop("userMusicDetailList") + + return { + "userId": data["userId"], + "rivalUserId": rival_id, + "length": music["length"], + "nextIndex": music["nextIndex"], + "userRivalMusicList": music["userMusicList"], + } + + @cached(2) + def util_generate_music_list(self, user_id: int) -> List: + music_detail = self.data.score.get_best_scores(user_id) + song_list = [] + + for md in music_detail: + found = False + tmp = md._asdict() + tmp.pop("user") + tmp.pop("id") + + for song in song_list: + if song["userMusicDetailList"][0]["musicId"] == tmp["musicId"]: + found = True + song["userMusicDetailList"].append(tmp) + song["length"] = len(song["userMusicDetailList"]) + break + + if not found: + song_list.append({ + "length": 1, + "userMusicDetailList": [tmp] + }) + + return song_list diff --git a/titles/ongeki/bright.py b/titles/ongeki/bright.py new file mode 100644 index 0000000..d92ed48 --- /dev/null +++ b/titles/ongeki/bright.py @@ -0,0 +1,21 @@ +from datetime import date, datetime, timedelta +from typing import Any, Dict +import pytz +import json + +from core.config import CoreConfig +from titles.ongeki.base import OngekiBase +from titles.ongeki.const import OngekiConstants +from titles.ongeki.config import OngekiConfig + +class OngekiBright(OngekiBase): + + def __init__(self, core_cfg: CoreConfig, game_cfg: OngekiConfig) -> None: + super().__init__(core_cfg, game_cfg) + self.version = OngekiConstants.VER_ONGEKI_BRIGHT + + def handle_get_game_setting_api_request(self, data: Dict) -> Dict: + ret = super().handle_get_game_setting_api_request(data) + ret["gameSetting"]["dataVersion"] = "1.30.00" + ret["gameSetting"]["onlineDataVersion"] = "1.30.00" + return ret diff --git a/titles/ongeki/config.py b/titles/ongeki/config.py new file mode 100644 index 0000000..722677c --- /dev/null +++ b/titles/ongeki/config.py @@ -0,0 +1,17 @@ +from core.config import CoreConfig + +class OngekiServerConfig(): + def __init__(self, parent_config: "OngekiConfig") -> None: + self.__config = parent_config + + @property + def enable(self) -> bool: + return CoreConfig.get_config_field(self.__config, 'ongeki', 'server', 'enable', default=True) + + @property + def loglevel(self) -> int: + return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'ongeki', 'server', 'loglevel', default="info")) + +class OngekiConfig(dict): + def __init__(self) -> None: + self.server = OngekiServerConfig(self) diff --git a/titles/ongeki/const.py b/titles/ongeki/const.py new file mode 100644 index 0000000..da9614e --- /dev/null +++ b/titles/ongeki/const.py @@ -0,0 +1,50 @@ +from typing import Final, Dict +from enum import Enum +class OngekiConstants(): + GAME_CODE = "SDDT" + + VER_ONGEKI = 0 + VER_ONGEKI_PLUS = 1 + VER_ONGEKI_SUMMER = 2 + VER_ONGEKI_SUMMER_PLUS = 3 + VER_ONGEKI_RED = 4 + VER_ONGEKI_RED_PLUS = 5 + VER_ONGEKI_BRIGHT = 6 + + EVT_TYPES: Enum = Enum('EVT_TYPES', [ + 'None', + 'Announcement', + 'Movie', + 'AddMyList', + 'UnlockChapter', + 'JewelEvent', + 'RankingEvent', + 'AcceptRankingEvent', + 'UnlockMusic', + 'UnlockCard', + 'UnlockTrophy', + 'UnlockNamePlate', + 'UnlockLimitBreakItem', + 'MissionEvent', + 'DailyBonus', + 'UnlockBossLockEarly', + 'UnlockPurchaseItem', + 'TechChallengeEvent', + 'AcceptTechChallengeEvent', + 'SilverJewelEvent', + ]) + + # The game expects the server to give Lunatic an ID of 10, while the game uses 4 internally... except in Music.xml + class DIFF_NAME(Enum): + Basic = 0 + Advanced = 1 + Expert = 2 + Master = 3 + Lunatic = 10 + + VERSION_NAMES = ("ONGEKI", "ONGEKI+", "ONGEKI Summer", "ONGEKI Summer+", "ONGEKI Red", "ONGEKI Red+", + "ONGEKI Bright") + + @classmethod + def game_ver_to_string(cls, ver: int): + return cls.VERSION_NAMES[ver] \ No newline at end of file diff --git a/titles/ongeki/database.py b/titles/ongeki/database.py new file mode 100644 index 0000000..a2168e4 --- /dev/null +++ b/titles/ongeki/database.py @@ -0,0 +1,14 @@ +from core.data import Data +from core.config import CoreConfig +from titles.ongeki.schema import OngekiItemData, OngekiProfileData, OngekiScoreData +from titles.ongeki.schema import OngekiStaticData, OngekiLogData + +class OngekiData(Data): + def __init__(self, cfg: CoreConfig) -> None: + super().__init__(cfg) + + self.item = OngekiItemData(cfg, self.session) + self.profile = OngekiProfileData(cfg, self.session) + self.score = OngekiScoreData(cfg, self.session) + self.static = OngekiStaticData(cfg, self.session) + self.log = OngekiLogData(cfg, self.session) \ No newline at end of file diff --git a/titles/ongeki/index.py b/titles/ongeki/index.py new file mode 100644 index 0000000..d3b107e --- /dev/null +++ b/titles/ongeki/index.py @@ -0,0 +1,114 @@ +from twisted.web.http import Request +import json +import inflection +import yaml +import string +import logging, coloredlogs +import zlib +from logging.handlers import TimedRotatingFileHandler + +from core.config import CoreConfig +from titles.ongeki.config import OngekiConfig +from titles.ongeki.const import OngekiConstants +from titles.ongeki.base import OngekiBase +from titles.ongeki.plus import OngekiPlus +from titles.ongeki.summer import OngekiSummer +from titles.ongeki.summerplus import OngekiSummerPlus +from titles.ongeki.red import OngekiRed +from titles.ongeki.redplus import OngekiRedPlus +from titles.ongeki.bright import OngekiBright + +class OngekiServlet(): + def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None: + self.core_cfg = core_cfg + self.game_cfg = OngekiConfig() + self.game_cfg.update(yaml.safe_load(open(f"{cfg_dir}/ongeki.yaml"))) + + self.versions = [ + OngekiBase(core_cfg, self.game_cfg), + OngekiPlus(core_cfg, self.game_cfg), + OngekiSummer(core_cfg, self.game_cfg), + OngekiSummerPlus(core_cfg, self.game_cfg), + OngekiRed(core_cfg, self.game_cfg), + OngekiRedPlus(core_cfg, self.game_cfg), + OngekiBright(core_cfg, self.game_cfg), + ] + + self.logger = logging.getLogger("ongeki") + log_fmt_str = "[%(asctime)s] Ongeki | %(levelname)s | %(message)s" + log_fmt = logging.Formatter(log_fmt_str) + fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.core_cfg.server.log_dir, "ongeki"), encoding='utf8', + when="d", backupCount=10) + + fileHandler.setFormatter(log_fmt) + + consoleHandler = logging.StreamHandler() + consoleHandler.setFormatter(log_fmt) + + self.logger.addHandler(fileHandler) + self.logger.addHandler(consoleHandler) + + self.logger.setLevel(self.game_cfg.server.loglevel) + coloredlogs.install(level=self.game_cfg.server.loglevel, logger=self.logger, fmt=log_fmt_str) + + def render_POST(self, request: Request, version: int, url_path: str) -> bytes: + req_raw = request.content.getvalue() + url_split = url_path.split("/") + internal_ver = 0 + endpoint = url_split[len(url_split) - 1] + + if version < 105: # 1.0 + internal_ver = OngekiConstants.VER_ONGEKI + elif version >= 105 and version < 110: # Plus + internal_ver = OngekiConstants.VER_ONGEKI_PLUS + elif version >= 110 and version < 115: # Summer + internal_ver = OngekiConstants.VER_ONGEKI_SUMMER + elif version >= 115 and version < 120: # Summer Plus + internal_ver = OngekiConstants.VER_ONGEKI_SUMMER_PLUS + elif version >= 120 and version < 125: # Red + internal_ver = OngekiConstants.VER_ONGEKI_RED + elif version >= 125 and version < 130: # Red Plus + internal_ver = OngekiConstants.VER_ONGEKI_RED_PLUS + elif version >= 130 and version < 135: # Red Plus + internal_ver = OngekiConstants.VER_ONGEKI_BRIGHT + + if all(c in string.hexdigits for c in endpoint) and len(endpoint) == 32: + # If we get a 32 character long hex string, it's a hash and we're + # doing encrypted. The likelyhood of false positives is low but + # technically not 0 + self.logger.error("Encryption not supported at this time") + + try: + unzip = zlib.decompress(req_raw) + + except zlib.error as e: + self.logger.error(f"Failed to decompress v{version} {endpoint} request -> {e}") + return zlib.compress("{\"stat\": \"0\"}".encode("utf-8")) + + req_data = json.loads(unzip) + + self.logger.info(f"v{version} {endpoint} request - {req_data}") + + func_to_find = "handle_" + inflection.underscore(endpoint) + "_request" + + try: + handler = getattr(self.versions[internal_ver], func_to_find) + 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: + self.logger.error(f"Error handling v{version} method {endpoint} - {e}") + return zlib.compress("{\"stat\": \"0\"}".encode("utf-8")) + + if resp == None: + resp = {'returnCode': 1} + + self.logger.info(f"Response {resp}") + + return zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8")) + + + \ No newline at end of file diff --git a/titles/ongeki/plus.py b/titles/ongeki/plus.py new file mode 100644 index 0000000..8875503 --- /dev/null +++ b/titles/ongeki/plus.py @@ -0,0 +1,17 @@ +from typing import Dict, Any + +from core.config import CoreConfig +from titles.ongeki.base import OngekiBase +from titles.ongeki.const import OngekiConstants +from titles.ongeki.config import OngekiConfig + +class OngekiPlus(OngekiBase): + def __init__(self, core_cfg: CoreConfig, game_cfg: OngekiConfig) -> None: + super().__init__(core_cfg, game_cfg) + self.version = OngekiConstants.VER_ONGEKI_PLUS + + def handle_get_game_setting_api_request(self, data: Dict) -> Dict: + ret = super().handle_get_game_setting_api_request(data) + ret["gameSetting"]["dataVersion"] = "1.05.00" + ret["gameSetting"]["onlineDataVersion"] = "1.05.00" + return ret diff --git a/titles/ongeki/read.py b/titles/ongeki/read.py new file mode 100644 index 0000000..8b1be3d --- /dev/null +++ b/titles/ongeki/read.py @@ -0,0 +1,89 @@ +from decimal import Decimal +import logging +import os +import re +import xml.etree.ElementTree as ET +from typing import Any, Dict, List, Optional + +from read import BaseReader +from core.config import CoreConfig +from titles.ongeki.database import OngekiData +from titles.ongeki.const import OngekiConstants +from titles.ongeki.config import OngekiConfig + +class OngekiReader(BaseReader): + def __init__(self, config: CoreConfig, version: int, bin_dir: Optional[str], opt_dir: Optional[str], extra: Optional[str]) -> None: + super().__init__(config, version, bin_dir, opt_dir, extra) + self.data = OngekiData(config) + + try: + self.logger.info(f"Start importer for {OngekiConstants.game_ver_to_string(version)}") + except IndexError: + self.logger.error(f"Invalid ongeki version {version}") + exit(1) + + def read(self) -> None: + data_dirs = [] + if self.bin_dir is not None: + data_dirs += self.get_data_directories(self.bin_dir) + + if self.opt_dir is not None: + data_dirs += self.get_data_directories(self.opt_dir) + + for dir in data_dirs: + self.read_events(f"{dir}/event") + self.read_music(f"{dir}/music") + + def read_events(self, base_dir: str) -> None: + self.logger.info(f"Reading events from {base_dir}...") + + for root, dirs, files in os.walk(base_dir): + for dir in dirs: + if os.path.exists(f"{root}/{dir}/Event.xml"): + with open(f"{root}/{dir}/Event.xml", "r", encoding="utf-8") as f: + troot = ET.fromstring(f.read()) + + name = troot.find('Name').find('str').text + id = int(troot.find('Name').find('id').text) + event_type = OngekiConstants.EVT_TYPES[troot.find('EventType').text].value + + + self.data.static.put_event(self.version, id, event_type, name) + self.logger.info(f"Added event {id}") + + def read_music(self, base_dir: str) -> None: + self.logger.info(f"Reading music from {base_dir}...") + + for root, dirs, files in os.walk(base_dir): + for dir in dirs: + if os.path.exists(f"{root}/{dir}/Music.xml"): + strdata = "" + + with open(f"{root}/{dir}/Music.xml", "r", encoding="utf-8") as f: + strdata = f.read() + + troot = ET.fromstring(strdata) + + if root is None: + continue + + name = troot.find('Name') + song_id = name.find('id').text + title = name.find('str').text + artist = troot.find('ArtistName').find('str').text + genre = troot.find('Genre').find('str').text + + fumens = troot.find("FumenData") + for fumens_data in fumens.findall('FumenData'): + path = fumens_data.find('FumenFile').find('path').text + if path is None or not os.path.exists(f"{root}/{dir}/{path}"): + continue + + chart_id = int(path.split(".")[0].split("_")[1]) + level = float( + f"{fumens_data.find('FumenConstIntegerPart').text}.{fumens_data.find('FumenConstFractionalPart').text}" + ) + + self.data.static.put_chart(self.version, song_id, chart_id, title, artist, genre, level) + self.logger.info(f"Added song {song_id} chart {chart_id}") + diff --git a/titles/ongeki/red.py b/titles/ongeki/red.py new file mode 100644 index 0000000..047286e --- /dev/null +++ b/titles/ongeki/red.py @@ -0,0 +1,17 @@ +from typing import Dict, Any + +from core.config import CoreConfig +from titles.ongeki.base import OngekiBase +from titles.ongeki.const import OngekiConstants +from titles.ongeki.config import OngekiConfig + +class OngekiRed(OngekiBase): + def __init__(self, core_cfg: CoreConfig, game_cfg: OngekiConfig) -> None: + super().__init__(core_cfg, game_cfg) + self.version = OngekiConstants.VER_ONGEKI_RED + + def handle_get_game_setting_api_request(self, data: Dict) -> Dict: + ret = super().handle_get_game_setting_api_request(data) + ret["gameSetting"]["dataVersion"] = "1.20.00" + ret["gameSetting"]["onlineDataVersion"] = "1.20.00" + return ret diff --git a/titles/ongeki/redplus.py b/titles/ongeki/redplus.py new file mode 100644 index 0000000..a4df205 --- /dev/null +++ b/titles/ongeki/redplus.py @@ -0,0 +1,23 @@ +from typing import Dict, Any + +from core.config import CoreConfig +from titles.ongeki.base import OngekiBase +from titles.ongeki.const import OngekiConstants +from titles.ongeki.config import OngekiConfig + +class OngekiRedPlus(OngekiBase): + def __init__(self, core_cfg: CoreConfig, game_cfg: OngekiConfig) -> None: + super().__init__(core_cfg, game_cfg) + self.version = OngekiConstants.VER_ONGEKI_RED_PLUS + + def handle_get_game_setting_api_request(self, data: Dict) -> Dict: + ret = super().handle_get_game_setting_api_request(data) + ret["gameSetting"]["dataVersion"] = "1.25.00" + ret["gameSetting"]["onlineDataVersion"] = "1.25.00" + ret["gameSetting"]["maxCountCharacter"] = 50 + ret["gameSetting"]["maxCountCard"] = 300 + ret["gameSetting"]["maxCountItem"] = 300 + ret["gameSetting"]["maxCountMusic"] = 50 + ret["gameSetting"]["maxCountMusicItem"] = 300 + ret["gameSetting"]["macCountRivalMusic"] = 300 + return ret diff --git a/titles/ongeki/schema/__init__.py b/titles/ongeki/schema/__init__.py new file mode 100644 index 0000000..9b1e1da --- /dev/null +++ b/titles/ongeki/schema/__init__.py @@ -0,0 +1,7 @@ +from titles.ongeki.schema.profile import OngekiProfileData +from titles.ongeki.schema.item import OngekiItemData +from titles.ongeki.schema.static import OngekiStaticData +from titles.ongeki.schema.score import OngekiScoreData +from titles.ongeki.schema.log import OngekiLogData + +__all__ = [OngekiProfileData, OngekiItemData, OngekiStaticData, OngekiScoreData, OngekiLogData] \ No newline at end of file diff --git a/titles/ongeki/schema/item.py b/titles/ongeki/schema/item.py new file mode 100644 index 0000000..cc09fda --- /dev/null +++ b/titles/ongeki/schema/item.py @@ -0,0 +1,526 @@ +from typing import Dict, Optional, List +from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_ +from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON +from sqlalchemy.schema import ForeignKey +from sqlalchemy.sql import func, select +from sqlalchemy.dialects.mysql import insert + +from core.data.schema import BaseData, metadata + +card = Table( + "ongeki_user_card", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")), + Column("cardId", Integer), + Column("digitalStock", Integer), + Column("analogStock", Integer), + Column("level", Integer), + Column("maxLevel", Integer), + Column("exp", Integer), + Column("printCount", Integer), + Column("useCount", Integer), + Column("isNew", Boolean), + Column("kaikaDate", String(25)), + Column("choKaikaDate", String(25)), + Column("skillId", Integer), + Column("isAcquired", Boolean), + Column("created", String(25)), + UniqueConstraint("user", "cardId", name="ongeki_user_card_uk"), + mysql_charset='utf8mb4' +) + +deck = Table( + "ongeki_user_deck", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")), + Column("deckId", Integer), + Column("cardId1", Integer), + Column("cardId2", Integer), + Column("cardId3", Integer), + UniqueConstraint("user", "deckId", name="ongeki_user_deck_uk"), + mysql_charset='utf8mb4' +) + +character = Table( + "ongeki_user_character", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")), + Column("characterId", Integer), + Column("costumeId", Integer), + Column("attachmentId", Integer), + Column("playCount", Integer), + Column("intimateLevel", Integer), + Column("intimateCount", Integer), + Column("intimateCountRewarded", Integer), + Column("intimateCountDate", String(25)), + Column("isNew", Boolean), + UniqueConstraint("user", "characterId", name="ongeki_user_character_uk"), + mysql_charset='utf8mb4' +) + +boss = Table ( + "ongeki_user_boss", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")), + Column("musicId", Integer), + Column("damage", Integer), + Column("isClear", Boolean), + Column("eventId", Integer), + UniqueConstraint("user", "musicId", "eventId", name="ongeki_user_boss_uk"), + mysql_charset='utf8mb4' +) + +story = Table ( + "ongeki_user_story", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")), + Column("storyId", Integer), + Column("jewelCount", Integer), + Column("lastChapterId", Integer), + Column("lastPlayMusicId", Integer), + Column("lastPlayMusicCategory", Integer), + Column("lastPlayMusicLevel", Integer), + UniqueConstraint("user", "storyId", name="ongeki_user_story_uk"), + mysql_charset='utf8mb4' +) + +chapter = Table( + "ongeki_user_chapter", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")), + Column("chapterId", Integer), + Column("jewelCount", Integer), + Column("isStoryWatched", Boolean), + Column("isClear", Boolean), + Column("lastPlayMusicId", Integer), + Column("lastPlayMusicCategory", Integer), + Column("lastPlayMusicLevel", Integer), + Column("skipTiming1", Integer), + Column("skipTiming2", Integer), + UniqueConstraint("user", "chapterId", name="ongeki_user_chapter_uk"), + mysql_charset='utf8mb4' +) + +item = Table( + "ongeki_user_item", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")), + Column("itemKind", Integer), + Column("itemId", Integer), + Column("stock", Integer), + Column("isValid", Boolean), + UniqueConstraint("user", "itemKind", "itemId", name="ongeki_user_item_uk"), + mysql_charset='utf8mb4' +) + +music_item = Table( + "ongeki_user_music_item", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")), + Column("musicId", Integer), + Column("status", Integer), + UniqueConstraint("user", "musicId", name="ongeki_user_music_item_uk"), + mysql_charset='utf8mb4' +) + +login_bonus = Table( + "ongeki_user_login_bonus", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")), + Column("bonusId", Integer), + Column("bonusCount", Integer), + Column("lastUpdateDate", String(25)), + UniqueConstraint("user", "bonusId", name="ongeki_user_login_bonus_uk"), + mysql_charset='utf8mb4' +) + +event_point = Table( + "ongeki_user_event_point", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")), + Column("eventId", Integer), + Column("point", Integer), + Column("isRankingRewarded", Boolean), + UniqueConstraint("user", "eventId", name="ongeki_user_event_point_uk"), + mysql_charset='utf8mb4' +) + +mission_point = Table( + "ongeki_user_mission_point", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")), + Column("eventId", Integer), + Column("point", Integer), + UniqueConstraint("user", "eventId", name="ongeki_user_mission_point_uk"), + mysql_charset='utf8mb4' +) + +scenerio = Table( + "ongeki_user_scenerio", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")), + Column("scenarioId", Integer), + Column("playCount", Integer), + UniqueConstraint("user", "scenarioId", name="ongeki_user_scenerio_uk"), + mysql_charset='utf8mb4' +) + +trade_item = Table( + "ongeki_user_trade_item", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")), + Column("chapterId", Integer), + Column("tradeItemId", Integer), + Column("tradeCount", Integer), + UniqueConstraint("user", "chapterId", "tradeItemId", name="ongeki_user_trade_item_uk"), + mysql_charset='utf8mb4' +) + +event_music = Table( + "ongeki_user_event_music", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")), + Column("eventId", Integer), + Column("type", Integer), + Column("musicId", Integer), + Column("level", Integer), + Column("techScoreMax", Integer), + Column("platinumScoreMax", Integer), + Column("techRecordDate", String(25)), + Column("isTechNewRecord", Boolean), + UniqueConstraint("user", "eventId", "type", "musicId", "level", name="ongeki_user_event_music"), + mysql_charset='utf8mb4' +) + +tech_event = Table( + "ongeki_user_tech_event", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")), + Column("eventId", Integer), + Column("totalTechScore", Integer), + Column("totalPlatinumScore", Integer), + Column("techRecordDate", String(25)), + Column("isRankingRewarded", Boolean), + Column("isTotalTechNewRecord", Boolean), + UniqueConstraint("user", "eventId", name="ongeki_user_tech_event_uk"), + mysql_charset='utf8mb4' +) + + +class OngekiItemData(BaseData): + def put_card(self, aime_id: int, card_data: Dict) -> Optional[int]: + card_data["user"] = aime_id + + sql = insert(card).values(**card_data) + conflict = sql.on_duplicate_key_update(**card_data) + result = self.execute(conflict) + + if result is None: + self.logger.warn(f"put_card: Failed to update! aime_id: {aime_id}") + return None + return result.lastrowid + + def get_cards(self, aime_id: int) -> Optional[List[Dict]]: + sql = select(card).where(card.c.user == aime_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def put_character(self, aime_id: int, character_data: Dict) -> Optional[int]: + character_data["user"] = aime_id + + sql = insert(character).values(**character_data) + conflict = sql.on_duplicate_key_update(**character_data) + result = self.execute(conflict) + + if result is None: + self.logger.warn(f"put_character: Failed to update! aime_id: {aime_id}") + return None + return result.lastrowid + + def get_characters(self, aime_id: int) -> Optional[List[Dict]]: + sql = select(character).where(character.c.user == aime_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def put_deck(self, aime_id: int, deck_data: Dict) -> Optional[int]: + deck_data["user"] = aime_id + + sql = insert(deck).values(**deck_data) + conflict = sql.on_duplicate_key_update(**deck_data) + result = self.execute(conflict) + + if result is None: + self.logger.warn(f"put_deck: Failed to update! aime_id: {aime_id}") + return None + return result.lastrowid + + def get_deck(self, aime_id: int, deck_id: int) -> Optional[Dict]: + sql = select(deck).where(and_(deck.c.user == aime_id, deck.c.deckId == deck_id)) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() + + def get_decks(self, aime_id: int) -> Optional[List[Dict]]: + sql = select(deck).where(deck.c.user == aime_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def put_boss(self, aime_id: int, boss_data: Dict) -> Optional[int]: + boss_data["user"] = aime_id + + sql = insert(boss).values(**boss_data) + conflict = sql.on_duplicate_key_update(**boss_data) + result = self.execute(conflict) + + if result is None: + self.logger.warn(f"put_boss: Failed to update! aime_id: {aime_id}") + return None + return result.lastrowid + + def put_story(self, aime_id: int, story_data: Dict) -> Optional[int]: + story_data["user"] = aime_id + + sql = insert(story).values(**story_data) + conflict = sql.on_duplicate_key_update(**story_data) + result = self.execute(conflict) + + if result is None: + self.logger.warn(f"put_story: Failed to update! aime_id: {aime_id}") + return None + return result.lastrowid + + def get_stories(self, aime_id: int) -> Optional[List[Dict]]: + sql = select(story).where(story.c.user == aime_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def put_chapter(self, aime_id: int, chapter_data: Dict) -> Optional[int]: + chapter_data["user"] = aime_id + + sql = insert(chapter).values(**chapter_data) + conflict = sql.on_duplicate_key_update(**chapter_data) + result = self.execute(conflict) + + if result is None: + self.logger.warn(f"put_chapter: Failed to update! aime_id: {aime_id}") + return None + return result.lastrowid + + def get_chapters(self, aime_id: int) -> Optional[List[Dict]]: + sql = select(chapter).where(chapter.c.user == aime_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def put_item(self, aime_id: int, item_data: Dict) -> Optional[int]: + item_data["user"] = aime_id + + sql = insert(item).values(**item_data) + conflict = sql.on_duplicate_key_update(**item_data) + result = self.execute(conflict) + + if result is None: + self.logger.warn(f"put_item: Failed to update! aime_id: {aime_id}") + return None + return result.lastrowid + + def get_item(self, aime_id: int, item_id: int, item_kind: int) -> Optional[Dict]: + sql = select(item).where(and_(item.c.user == aime_id, item.c.itemId == item_id)) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() + + def get_items(self, aime_id: int, item_kind: int = None) -> Optional[List[Dict]]: + if item_kind is None: + sql = select(item).where(item.c.user == aime_id) + else: + sql = select(item).where(and_(item.c.user == aime_id, item.c.itemKind == item_kind)) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def put_music_item(self, aime_id: int, music_item_data: Dict) -> Optional[int]: + music_item_data["user"] = aime_id + + sql = insert(music_item).values(**music_item_data) + conflict = sql.on_duplicate_key_update(**music_item_data) + result = self.execute(conflict) + + if result is None: + self.logger.warn(f"put_music_item: Failed to update! aime_id: {aime_id}") + return None + return result.lastrowid + + def get_music_items(self, aime_id: int) -> Optional[List[Dict]]: + sql = select(music_item).where(music_item.c.user == aime_id) + result = self.execute(sql) + + if result is None: return None + return result.fetchall() + + def put_login_bonus(self, aime_id: int, login_bonus_data: Dict) -> Optional[int]: + login_bonus_data["user"] = aime_id + + sql = insert(login_bonus).values(**login_bonus_data) + conflict = sql.on_duplicate_key_update(**login_bonus_data) + result = self.execute(conflict) + + if result is None: + self.logger.warn(f"put_login_bonus: Failed to update! aime_id: {aime_id}") + return None + return result.lastrowid + + def get_login_bonuses(self, aime_id: int) -> Optional[List[Dict]]: + sql = select(login_bonus).where(login_bonus.c.user == aime_id) + result = self.execute(sql) + + if result is None: return None + return result.fetchall() + + def put_mission_point(self, aime_id: int, mission_point_data: Dict) -> Optional[int]: + mission_point_data["user"] = aime_id + + sql = insert(mission_point).values(**mission_point_data) + conflict = sql.on_duplicate_key_update(**mission_point_data) + result = self.execute(conflict) + + if result is None: + self.logger.warn(f"put_mission_point: Failed to update! aime_id: {aime_id}") + return None + return result.lastrowid + + def get_mission_points(self, aime_id: int) -> Optional[List[Dict]]: + sql = select(mission_point).where(mission_point.c.user == aime_id) + result = self.execute(sql) + + if result is None: return None + return result.fetchall() + + def put_event_point(self, aime_id: int, event_point_data: Dict) -> Optional[int]: + event_point_data["user"] = aime_id + + sql = insert(event_point).values(**event_point_data) + conflict = sql.on_duplicate_key_update(**event_point_data) + result = self.execute(conflict) + + if result is None: + self.logger.warn(f"put_event_point: Failed to update! aime_id: {aime_id}") + return None + return result.lastrowid + + def get_event_points(self, aime_id: int) -> Optional[List[Dict]]: + sql = select(event_point).where(event_point.c.user == aime_id) + result = self.execute(sql) + + if result is None: return None + return result.fetchall() + + def put_scenerio(self, aime_id: int, scenerio_data: Dict) -> Optional[int]: + scenerio_data["user"] = aime_id + + sql = insert(scenerio).values(**scenerio_data) + conflict = sql.on_duplicate_key_update(**scenerio_data) + result = self.execute(conflict) + + if result is None: + self.logger.warn(f"put_scenerio: Failed to update! aime_id: {aime_id}") + return None + return result.lastrowid + + def get_scenerios(self, aime_id: int) -> Optional[List[Dict]]: + sql = select(scenerio).where(scenerio.c.user == aime_id) + result = self.execute(sql) + + if result is None: return None + return result.fetchall() + + def put_trade_item(self, aime_id: int, trade_item_data: Dict) -> Optional[int]: + trade_item_data["user"] = aime_id + + sql = insert(trade_item).values(**trade_item_data) + conflict = sql.on_duplicate_key_update(**trade_item_data) + result = self.execute(conflict) + + if result is None: + self.logger.warn(f"put_trade_item: Failed to update! aime_id: {aime_id}") + return None + return result.lastrowid + + def get_trade_items(self, aime_id: int) -> Optional[List[Dict]]: + sql = select(trade_item).where(trade_item.c.user == aime_id) + result = self.execute(sql) + + if result is None: return None + return result.fetchall() + + def put_event_music(self, aime_id: int, event_music_data: Dict) -> Optional[int]: + event_music_data["user"] = aime_id + + sql = insert(event_music).values(**event_music_data) + conflict = sql.on_duplicate_key_update(**event_music_data) + result = self.execute(conflict) + + if result is None: + self.logger.warn(f"put_event_music: Failed to update! aime_id: {aime_id}") + return None + return result.lastrowid + + def get_event_music(self, aime_id: int) -> Optional[List[Dict]]: + sql = select(event_music).where(event_music.c.user == aime_id) + result = self.execute(sql) + + if result is None: return None + return result.fetchall() + + def put_tech_event(self, aime_id: int, tech_event_data: Dict) -> Optional[int]: + tech_event_data["user"] = aime_id + + sql = insert(tech_event).values(**tech_event_data) + conflict = sql.on_duplicate_key_update(**tech_event_data) + result = self.execute(conflict) + + if result is None: + self.logger.warn(f"put_tech_event: Failed to update! aime_id: {aime_id}") + return None + return result.lastrowid + + def get_tech_event(self, aime_id: int) -> Optional[List[Dict]]: + sql = select(tech_event).where(tech_event.c.user == aime_id) + result = self.execute(sql) + + if result is None: return None + return result.fetchall() + + def get_bosses(self, aime_id: int) -> Optional[List[Dict]]: + sql = select(boss).where(boss.c.user == aime_id) + result = self.execute(sql) + + if result is None: return None + return result.fetchall() \ No newline at end of file diff --git a/titles/ongeki/schema/log.py b/titles/ongeki/schema/log.py new file mode 100644 index 0000000..67ed778 --- /dev/null +++ b/titles/ongeki/schema/log.py @@ -0,0 +1,55 @@ +from typing import Dict, Optional +from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_ +from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON +from sqlalchemy.schema import ForeignKey +from sqlalchemy.sql import func, select +from sqlalchemy.dialects.mysql import insert + +from core.data.schema import BaseData, metadata + +gp_log = Table( + "ongeki_gp_log", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")), + Column("usedCredit", Integer), + Column("placeName", String(255)), + Column("trxnDate", String(255)), + Column("placeId", Integer), # Making this an FK would mess with people playing with default KC + Column("kind", Integer), + Column("pattern", Integer), + Column("currentGP", Integer), + mysql_charset='utf8mb4' +) + +session_log = Table( + "ongeki_session_log", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")), + Column("sortNumber", Integer), + Column("placeId", Integer), + Column("playDate", String(10)), + Column("userPlayDate", String(25)), + Column("isPaid", Boolean), + mysql_charset='utf8mb4' +) + +class OngekiLogData(BaseData): + def put_gp_log(self, aime_id: Optional[int], used_credit: int, place_name: str, tx_date: str, place_id: int, + kind: int, pattern: int, current_gp: int) -> Optional[int]: + sql = insert(gp_log).values( + user=aime_id, + usedCredit=used_credit, + placeName=place_name, + trxnDate=tx_date, + placeId=place_id, + kind=kind, + pattern=pattern, + currentGP=current_gp, + ) + + result = self.execute(sql) + if result is None: + self.logger.warn(f"put_gp_log: Failed to insert GP log! aime_id: {aime_id} kind {kind} pattern {pattern} current_gp {current_gp}") + return result.lastrowid \ No newline at end of file diff --git a/titles/ongeki/schema/profile.py b/titles/ongeki/schema/profile.py new file mode 100644 index 0000000..bdff67d --- /dev/null +++ b/titles/ongeki/schema/profile.py @@ -0,0 +1,447 @@ +from typing import Dict, List, Optional +from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_ +from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, BigInteger +from sqlalchemy.engine.base import Connection +from sqlalchemy.schema import ForeignKey +from sqlalchemy.sql import func, select +from sqlalchemy.engine import Row +from sqlalchemy.dialects.mysql import insert + +from core.data.schema import BaseData, metadata +from core.config import CoreConfig + +# Cammel case column names technically don't follow the other games but +# it makes it way easier on me to not fuck with what the games has +profile = Table( + "ongeki_profile_data", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("version", Integer, nullable=False), + Column("userName", String(8)), + Column("level", Integer), + Column("reincarnationNum", Integer), + Column("exp", Integer), + Column("point", Integer), + Column("totalPoint", Integer), + Column("playCount", Integer), + Column("jewelCount", Integer), + Column("totalJewelCount", Integer), + Column("medalCount", Integer), + Column("playerRating", Integer), + Column("highestRating", Integer), + Column("battlePoint", Integer), + Column("nameplateId", Integer), + Column("trophyId", Integer), + Column("cardId", Integer), + Column("characterId", Integer), + Column("characterVoiceNo", Integer), + Column("tabSetting", Integer), + Column("tabSortSetting", Integer), + Column("cardCategorySetting", Integer), + Column("cardSortSetting", Integer), + Column("playedTutorialBit", Integer), + Column("firstTutorialCancelNum", Integer), + Column("sumTechHighScore", BigInteger), + Column("sumTechBasicHighScore", BigInteger), + Column("sumTechAdvancedHighScore", BigInteger), + Column("sumTechExpertHighScore", BigInteger), + Column("sumTechMasterHighScore", BigInteger), + Column("sumTechLunaticHighScore", BigInteger), + Column("sumBattleHighScore", BigInteger), + Column("sumBattleBasicHighScore", BigInteger), + Column("sumBattleAdvancedHighScore", BigInteger), + Column("sumBattleExpertHighScore", BigInteger), + Column("sumBattleMasterHighScore", BigInteger), + Column("sumBattleLunaticHighScore", BigInteger), + Column("eventWatchedDate", String(255)), + Column("cmEventWatchedDate", String(255)), + Column("firstGameId", String(8)), + Column("firstRomVersion", String(8)), + Column("firstDataVersion", String(8)), + Column("firstPlayDate", String(255)), + Column("lastGameId", String(8)), + Column("lastRomVersion", String(8)), + Column("lastDataVersion", String(8)), + Column("compatibleCmVersion", String(8)), + Column("lastPlayDate", String(255)), + Column("lastPlaceId", Integer), + Column("lastPlaceName", String(255)), + Column("lastRegionId", Integer), + Column("lastRegionName", String(255)), + Column("lastAllNetId", Integer), + Column("lastClientId", String(16)), + Column("lastUsedDeckId", Integer), + Column("lastPlayMusicLevel", Integer), + Column("banStatus", Integer, server_default="0"), + Column("rivalScoreCategorySetting", Integer, server_default="0"), + Column("overDamageBattlePoint", Integer, server_default="0"), + Column("bestBattlePoint", Integer, server_default="0"), + Column("lastEmoneyBrand", Integer, server_default="0"), + UniqueConstraint("user", "version", name="ongeki_profile_profile_uk"), + mysql_charset='utf8mb4' +) + +# No point setting defaults since the game sends everything on profile creation anyway +option = Table( + "ongeki_profile_option", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("optionSet", Integer), + Column("speed", Integer), + Column("mirror", Integer), + Column("judgeTiming", Integer), + Column("judgeAdjustment", Integer), + Column("abort", Integer), + Column("tapSound", Integer), + Column("volGuide", Integer), + Column("volAll", Integer), + Column("volTap", Integer), + Column("volCrTap", Integer), + Column("volHold", Integer), + Column("volSide", Integer), + Column("volFlick", Integer), + Column("volBell", Integer), + Column("volEnemy", Integer), + Column("volSkill", Integer), + Column("volDamage", Integer), + Column("colorField", Integer), + Column("colorLaneBright", Integer), + Column("colorLane", Integer), + Column("colorSide", Integer), + Column("effectDamage", Integer), + Column("effectPos", Integer), + Column("judgeDisp", Integer), + Column("judgePos", Integer), + Column("judgeBreak", Integer), + Column("judgeHit", Integer), + Column("platinumBreakDisp", Integer), + Column("judgeCriticalBreak", Integer), + Column("matching", Integer), + Column("dispPlayerLv", Integer), + Column("dispRating", Integer), + Column("dispBP", Integer), + Column("headphone", Integer), + Column("stealthField", Integer), + Column("colorWallBright", Integer), + UniqueConstraint("user", name="ongeki_profile_option_uk"), + mysql_charset='utf8mb4' +) + +activity = Table( + "ongeki_profile_activity", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("kind", Integer), + Column("activityId", Integer), + Column("sortNumber", Integer), + Column("param1", Integer), + Column("param2", Integer), + Column("param3", Integer), + Column("param4", Integer), + UniqueConstraint("user", "kind", "activityId", name="ongeki_profile_activity_uk"), + mysql_charset='utf8mb4' +) + +recent_rating = Table( + "ongeki_profile_recent_rating", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("recentRating", JSON), + UniqueConstraint("user", name="ongeki_profile_recent_rating_uk"), + mysql_charset='utf8mb4' +) + +rating_log = Table( + "ongeki_profile_rating_log", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("highestRating", Integer), + Column("dataVersion", String(10)), + UniqueConstraint("user", "dataVersion", name="ongeki_profile_rating_log_uk"), + mysql_charset='utf8mb4' +) + +region = Table( + "ongeki_profile_region", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("regionId", Integer), + Column("playCount", Integer), + Column("created", String(25)), + UniqueConstraint("user", "regionId", name="ongeki_profile_region_uk"), + mysql_charset='utf8mb4' +) + +training_room = Table ( + "ongeki_profile_training_room", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")), + Column("roomId", Integer), + Column("authKey", Integer), + Column("cardId", Integer), + Column("valueDate", String(25)), + UniqueConstraint("user", "roomId", name="ongeki_profile_training_room_uk"), + mysql_charset='utf8mb4' +) + +kop = Table ( + "ongeki_profile_kop", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")), + Column("authKey", Integer), + Column("kopId", Integer), + Column("areaId", Integer), + Column("totalTechScore", Integer), + Column("totalPlatinumScore", Integer), + Column("techRecordDate", String(25)), + Column("isTotalTechNewRecord", Boolean), + UniqueConstraint("user", "kopId", name="ongeki_profile_kop_uk"), + mysql_charset='utf8mb4' +) + +rival = Table( + "ongeki_profile_rival", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")), + Column("rivalUserId", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")), + UniqueConstraint("user", "rivalUserId", name="ongeki_profile_rival_uk"), + mysql_charset='utf8mb4' +) + +class OngekiProfileData(BaseData): + def __init__(self, cfg: CoreConfig, conn: Connection) -> None: + super().__init__(cfg, conn) + 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" + + def get_profile_name(self, aime_id: int, version: int) -> Optional[str]: + sql = select(profile.c.userName).where(and_(profile.c.user == aime_id, profile.c.version == version)) + + result = self.execute(sql) + if result is None: return None + + row = result.fetchone() + if row is None: return None + + return row["userName"] + + def get_profile_preview(self, aime_id: int, version: int) -> Optional[Row]: + sql = select([profile, option]).join(option, profile.c.user == option.c.user).filter( + and_(profile.c.user == aime_id, profile.c.version == version) + ) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() + + def get_profile_data(self, aime_id: int, version: int) -> Optional[Row]: + sql = select(profile).where(and_( + profile.c.user == aime_id, + profile.c.version == version, + )) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() + + def get_profile_options(self, aime_id: int) -> Optional[Row]: + sql = select(option).where(and_( + option.c.user == aime_id, + )) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() + + def get_profile_recent_rating(self, aime_id: int) -> Optional[List[Row]]: + sql = select(recent_rating).where(recent_rating.c.user == aime_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() + + def get_profile_rating_log(self, aime_id: int) -> Optional[List[Row]]: + sql = select(rating_log).where(recent_rating.c.user == aime_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def get_profile_activity(self, aime_id: int, kind: int = None) -> Optional[List[Row]]: + sql = select(activity).where(and_( + activity.c.user == aime_id, + (activity.c.kind == kind) if kind is not None else True + )) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def get_kop(self, aime_id: int) -> Optional[List[Row]]: + sql = select(kop).where(kop.c.user == aime_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def get_rivals(self, aime_id: int) -> Optional[List[Row]]: + sql = select(rival.c.rivalUserId).where(rival.c.user == aime_id) + + result = self.execute(sql) + if result is None: + return None + return result.fetchall() + + def put_profile_data(self, aime_id: int, version: int, data: Dict) -> Optional[int]: + data["user"] = aime_id + data["version"] = version + data.pop("accessCode") + + sql = insert(profile).values(**data) + conflict = sql.on_duplicate_key_update(**data) + result = self.execute(conflict) + + if result is None: + self.logger.warn(f"put_profile_data: Failed to update! aime_id: {aime_id}") + return None + return result.lastrowid + + def put_profile_options(self, aime_id: int, options_data: Dict) -> Optional[int]: + options_data["user"] = aime_id + + sql = insert(option).values(**options_data) + conflict = sql.on_duplicate_key_update(**options_data) + result = self.execute(conflict) + + if result is None: + self.logger.warn(f"put_profile_options: Failed to update! aime_id: {aime_id}") + return None + return result.lastrowid + + def put_profile_recent_rating(self, aime_id: int, recent_rating_data: List[Dict]) -> Optional[int]: + sql = insert(recent_rating).values( + user=aime_id, + recentRating=recent_rating_data + ) + + conflict = sql.on_duplicate_key_update( + recentRating=recent_rating_data + ) + + result = self.execute(conflict) + if result is None: + self.logger.warn(f"put_profile_recent_rating: failed to update recent rating! aime_id {aime_id}") + return None + return result.lastrowid + + def put_profile_bp_list(self, aime_id: int, bp_base_list: List[Dict]) -> Optional[int]: + pass + + def put_profile_rating_log(self, aime_id: int, data_version: str, highest_rating: int) -> Optional[int]: + sql = insert(rating_log).values( + user=aime_id, + dataVersion=data_version, + highestRating=highest_rating + ) + + conflict = sql.on_duplicate_key_update( + highestRating=highest_rating + ) + + result = self.execute(conflict) + if result is None: + self.logger.warn(f"put_profile_rating_log: failed to update rating log! aime_id {aime_id} data_version {data_version} highest_rating {highest_rating}") + return None + return result.lastrowid + + def put_profile_activity(self, aime_id: int, kind: int, activity_id: int, sort_num: int, + p1: int, p2: int, p3: int, p4: int) -> Optional[int]: + sql = insert(activity).values( + user=aime_id, + kind=kind, + activityId=activity_id, + sortNumber=sort_num, + param1=p1, + param2=p2, + param3=p3, + param4=p4 + ) + + conflict = sql.on_duplicate_key_update( + sortNumber=sort_num, + param1=p1, + param2=p2, + param3=p3, + param4=p4 + ) + + result = self.execute(conflict) + if result is None: + self.logger.warn(f"put_profile_activity: failed to put activity! aime_id {aime_id} kind {kind} activity_id {activity_id}") + return None + return result.lastrowid + + def put_profile_region(self, aime_id: int, region: int, date: str) -> Optional[int]: + sql = insert(activity).values( + user=aime_id, + region=region, + playCount=1, + created=date + ) + + conflict = sql.on_duplicate_key_update( + playCount=activity.c.playCount + 1, + ) + + result = self.execute(conflict) + if result is None: + self.logger.warn(f"put_profile_region: failed to update! aime_id {aime_id} region {region}") + return None + return result.lastrowid + + def put_training_room(self, aime_id: int, room_detail: Dict) -> Optional[int]: + room_detail["user"] = aime_id + + sql = insert(training_room).values(**room_detail) + conflict = sql.on_duplicate_key_update(**room_detail) + result = self.execute(conflict) + + if result is None: + self.logger.warn(f"put_best_score: Failed to add score! aime_id: {aime_id}") + return None + return result.lastrowid + + def put_kop(self, aime_id: int, kop_data: Dict) -> Optional[int]: + kop_data["user"] = aime_id + + sql = insert(kop).values(**kop_data) + conflict = sql.on_duplicate_key_update(**kop_data) + result = self.execute(conflict) + + if result is None: + self.logger.warn(f"put_kop: Failed to add score! aime_id: {aime_id}") + return None + return result.lastrowid + + def put_rival(self, aime_id: int, rival_id: int) -> Optional[int]: + sql = insert(rival).values( + user = aime_id, + rivalUserId = rival_id + ) + + conflict = sql.on_duplicate_key_update(rival = rival_id) + + result = self.execute(conflict) + if result is None: + self.logger.warn(f"put_rival: failed to update! aime_id: {aime_id}, rival_id: {rival_id}") + return None + return result.lastrowid diff --git a/titles/ongeki/schema/score.py b/titles/ongeki/schema/score.py new file mode 100644 index 0000000..08a6a86 --- /dev/null +++ b/titles/ongeki/schema/score.py @@ -0,0 +1,161 @@ +from typing import Dict, List, Optional +from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_ +from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, Float +from sqlalchemy.schema import ForeignKey +from sqlalchemy.sql import func, select +from sqlalchemy.dialects.mysql import insert + +from core.data.schema import BaseData, metadata + +score_best = Table( + "ongeki_score_best", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("musicId", Integer, nullable=False), + Column("level", Integer, nullable=False), + Column("playCount", Integer, nullable=False), + Column("techScoreMax", Integer, nullable=False), + Column("techScoreRank", Integer, nullable=False), + Column("battleScoreMax", Integer, nullable=False), + Column("battleScoreRank", Integer, nullable=False), + Column("maxComboCount", Integer, nullable=False), + Column("maxOverKill", Float, nullable=False), + Column("maxTeamOverKill", Float, nullable=False), + Column("isFullBell", Boolean, nullable=False), + Column("isFullCombo", Boolean, nullable=False), + Column("isAllBreake", Boolean, nullable=False), + Column("isLock", Boolean, nullable=False), + Column("clearStatus", Boolean, nullable=False), + Column("isStoryWatched", Boolean, nullable=False), + UniqueConstraint("user", "musicId", "level", name="ongeki_best_score_uk"), + mysql_charset='utf8mb4' +) + +playlog = Table( + "ongeki_score_playlog", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("sortNumber", Integer), + Column("placeId", Integer), + Column("placeName", String(255)), + Column("playDate", TIMESTAMP), + Column("userPlayDate", TIMESTAMP), + Column("musicId", Integer), + Column("level", Integer), + Column("playKind", Integer), + Column("eventId", Integer), + Column("eventName", String(255)), + Column("eventPoint", Integer), + Column("playedUserId1", Integer), + Column("playedUserId2", Integer), + Column("playedUserId3", Integer), + Column("playedUserName1", String(8)), + Column("playedUserName2", String(8)), + Column("playedUserName3", String(8)), + Column("playedMusicLevel1", Integer), + Column("playedMusicLevel2", Integer), + Column("playedMusicLevel3", Integer), + Column("cardId1", Integer), + Column("cardId2", Integer), + Column("cardId3", Integer), + Column("cardLevel1", Integer), + Column("cardLevel2", Integer), + Column("cardLevel3", Integer), + Column("cardAttack1", Integer), + Column("cardAttack2", Integer), + Column("cardAttack3", Integer), + Column("bossCharaId", Integer), + Column("bossLevel", Integer), + Column("bossAttribute", Integer), + Column("clearStatus", Integer), + Column("techScore", Integer), + Column("techScoreRank", Integer), + Column("battleScore", Integer), + Column("battleScoreRank", Integer), + Column("maxCombo", Integer), + Column("judgeMiss", Integer), + Column("judgeHit", Integer), + Column("judgeBreak", Integer), + Column("judgeCriticalBreak", Integer), + Column("rateTap", Integer), + Column("rateHold", Integer), + Column("rateFlick", Integer), + Column("rateSideTap", Integer), + Column("rateSideHold", Integer), + Column("bellCount", Integer), + Column("totalBellCount", Integer), + Column("damageCount", Integer), + Column("overDamage", Integer), + Column("isTechNewRecord", Boolean), + Column("isBattleNewRecord", Boolean), + Column("isOverDamageNewRecord", Boolean), + Column("isFullCombo", Boolean), + Column("isFullBell", Boolean), + Column("isAllBreak", Boolean), + Column("playerRating", Integer), + Column("battlePoint", Integer), + mysql_charset='utf8mb4' +) + +tech_count = Table( + "ongeki_score_tech_count", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("levelId", Integer, nullable=False), + Column("allBreakCount", Integer), + Column("allBreakPlusCount", Integer), + UniqueConstraint("user", "levelId", name="ongeki_tech_count_uk"), + mysql_charset='utf8mb4' +) + +class OngekiScoreData(BaseData): + def get_tech_count(self, aime_id: int) -> Optional[List[Dict]]: + return [] + + def put_tech_count(self, aime_id: int, tech_count_data: Dict) -> Optional[int]: + tech_count_data["user"] = aime_id + + sql = insert(tech_count).values(**tech_count_data) + conflict = sql.on_duplicate_key_update(**tech_count_data) + result = self.execute(conflict) + + if result is None: + self.logger.warn(f"put_tech_count: Failed to update! aime_id: {aime_id}") + return None + return result.lastrowid + + def get_best_scores(self, aime_id: int) -> Optional[List[Dict]]: + sql = select(score_best).where(score_best.c.user == aime_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def get_best_score(self, aime_id: int, song_id: int, chart_id: int = None) -> Optional[List[Dict]]: + return [] + + def put_best_score(self, aime_id: int, music_detail: Dict) -> Optional[int]: + music_detail["user"] = aime_id + + sql = insert(score_best).values(**music_detail) + conflict = sql.on_duplicate_key_update(**music_detail) + result = self.execute(conflict) + + if result is None: + self.logger.warn(f"put_best_score: Failed to add score! aime_id: {aime_id}") + return None + return result.lastrowid + + def put_playlog(self, aime_id: int, playlog_data: Dict) -> Optional[int]: + playlog_data["user"] = aime_id + + sql = insert(playlog).values(**playlog_data) + + result = self.execute(sql) + if result is None: + self.logger.warn(f"put_playlog: Failed to add playlog! aime_id: {aime_id}") + return None + return result.lastrowid \ No newline at end of file diff --git a/titles/ongeki/schema/static.py b/titles/ongeki/schema/static.py new file mode 100644 index 0000000..e98ec58 --- /dev/null +++ b/titles/ongeki/schema/static.py @@ -0,0 +1,119 @@ +from typing import Dict, List, Optional +from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_ +from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, Float +from sqlalchemy.schema import ForeignKey +from sqlalchemy.sql import func, select +from sqlalchemy.engine import Row +from sqlalchemy.dialects.mysql import insert + +from core.data.schema import BaseData, metadata + +events = Table( + "ongeki_static_events", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("version", Integer), + Column("eventId", Integer), + Column("type", Integer), + Column("name", String(255)), + Column("enabled", Boolean, server_default="1"), + UniqueConstraint("version", "eventId", "type", name="ongeki_static_events_uk"), + mysql_charset='utf8mb4' +) + + +music = Table( + "ongeki_static_music", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("version", Integer), + Column("songId", Integer), + Column("chartId", Integer), + Column("title", String(255)), + Column("artist", String(255)), + Column("genre", String(255)), + Column("level", Float), + UniqueConstraint("version", "songId", "chartId", name="ongeki_static_music_uk"), + mysql_charset='utf8mb4' +) + +class OngekiStaticData(BaseData): + def put_event(self, version: int, event_id: int, event_type: int, event_name: str) -> Optional[int]: + sql = insert(events).values( + version = version, + eventId = event_id, + type = event_type, + name = event_name, + ) + + conflict = sql.on_duplicate_key_update( + name = event_name, + ) + + result = self.execute(conflict) + if result is None: + self.logger.warn(f"Failed to insert event! event_id {event_id}") + return None + return result.lastrowid + + def get_event(self, version: int, event_id: int) -> Optional[List[Dict]]: + sql = select(events).where(and_(events.c.version == version, events.c.eventId == event_id)) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def get_events(self, version: int) -> Optional[List[Dict]]: + sql = select(events).where(events.c.version == version) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def get_enabled_events(self, version: int) -> Optional[List[Dict]]: + sql = select(events).where(and_(events.c.version == version, events.c.enabled == True)) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def put_chart(self, version: int, song_id: int, chart_id: int, title: str, artist: str, genre: str, level: float) -> Optional[int]: + sql = insert(music).values( + version = version, + songId = song_id, + chartId = chart_id, + title = title, + artist = artist, + genre = genre, + level = level, + ) + + conflict = sql.on_duplicate_key_update( + title = title, + artist = artist, + genre = genre, + level = level, + ) + + result = self.execute(conflict) + if result is None: + self.logger.warn(f"Failed to insert chart! song_id: {song_id}, chart_id: {chart_id}") + return None + return result.lastrowid + + def get_chart(self, version: int, song_id: int, chart_id: int = None) -> Optional[List[Dict]]: + pass + + def get_music(self, version: int) -> Optional[List[Dict]]: + pass + + def get_music_chart(self, version: int, song_id: int, chart_id: int) -> Optional[List[Row]]: + sql = select(music).where(and_( + music.c.version == version, + music.c.songId == song_id, + music.c.chartId == chart_id + )) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() diff --git a/titles/ongeki/summer.py b/titles/ongeki/summer.py new file mode 100644 index 0000000..24ed290 --- /dev/null +++ b/titles/ongeki/summer.py @@ -0,0 +1,17 @@ +from typing import Dict, Any + +from core.config import CoreConfig +from titles.ongeki.base import OngekiBase +from titles.ongeki.const import OngekiConstants +from titles.ongeki.config import OngekiConfig + +class OngekiSummer(OngekiBase): + def __init__(self, core_cfg: CoreConfig, game_cfg: OngekiConfig) -> None: + super().__init__(core_cfg, game_cfg) + self.version = OngekiConstants.VER_ONGEKI_SUMMER + + def handle_get_game_setting_api_request(self, data: Dict) -> Dict: + ret = super().handle_get_game_setting_api_request(data) + ret["gameSetting"]["dataVersion"] = "1.10.00" + ret["gameSetting"]["onlineDataVersion"] = "1.10.00" + return ret diff --git a/titles/ongeki/summerplus.py b/titles/ongeki/summerplus.py new file mode 100644 index 0000000..188e618 --- /dev/null +++ b/titles/ongeki/summerplus.py @@ -0,0 +1,17 @@ +from typing import Dict, Any + +from core.config import CoreConfig +from titles.ongeki.base import OngekiBase +from titles.ongeki.const import OngekiConstants +from titles.ongeki.config import OngekiConfig + +class OngekiSummerPlus(OngekiBase): + def __init__(self, core_cfg: CoreConfig, game_cfg: OngekiConfig) -> None: + super().__init__(core_cfg, game_cfg) + self.version = OngekiConstants.VER_ONGEKI_SUMMER_PLUS + + def handle_get_game_setting_api_request(self, data: Dict) -> Dict: + ret = super().handle_get_game_setting_api_request(data) + ret["gameSetting"]["dataVersion"] = "1.15.00" + ret["gameSetting"]["onlineDataVersion"] = "1.15.00" + return ret diff --git a/titles/wacca/__init__.py b/titles/wacca/__init__.py new file mode 100644 index 0000000..c0351da --- /dev/null +++ b/titles/wacca/__init__.py @@ -0,0 +1,18 @@ +from titles.wacca.const import WaccaConstants +from titles.wacca.index import WaccaServlet +from titles.wacca.read import WaccaReader +from titles.wacca.database import WaccaData + +index = WaccaServlet +database = WaccaData +reader = WaccaReader + +use_default_title = True +include_protocol = True +title_secure = False +game_codes = [WaccaConstants.GAME_CODE] +trailing_slash = False +use_default_host = False +host = "" + +current_schema_version = 3 \ No newline at end of file diff --git a/titles/wacca/base.py b/titles/wacca/base.py new file mode 100644 index 0000000..598f1fc --- /dev/null +++ b/titles/wacca/base.py @@ -0,0 +1,941 @@ +from typing import Any, List, Dict +import logging +from math import floor + +from datetime import datetime, timedelta + +from core.config import CoreConfig +from titles.wacca.config import WaccaConfig +from titles.wacca.const import WaccaConstants +from titles.wacca.database import WaccaData + +from titles.wacca.handlers import * + +class WaccaBase(): + def __init__(self, cfg: CoreConfig, game_cfg: WaccaConfig) -> None: + self.config = cfg # Config file + self.game_config = game_cfg # Game Config file + self.game = WaccaConstants.GAME_CODE # Game code + self.version = WaccaConstants.VER_WACCA # Game version + self.data = WaccaData(cfg) # Database + self.logger = logging.getLogger("wacca") + self.srvtime = datetime.now() + self.season = 1 + + self.OPTIONS_DEFAULTS: Dict[str, Any] = { + "note_speed": 5, + "field_mask": 0, + "note_sound": 105001, + "note_color": 203001, + "bgm_volume": 10, + "bg_video": 0, + + "mirror": 0, + "judge_display_pos": 0, + "judge_detail_display": 0, + "measure_guidelines": 1, + "guideline_mask": 1, + "judge_line_timing_adjust": 10, + "note_design": 3, + "bonus_effect": 1, + "chara_voice": 1, + "score_display_method": 0, + "give_up": 0, + "guideline_spacing": 1, + "center_display": 1, + "ranking_display": 1, + "stage_up_icon_display": 1, + "rating_display": 1, + "player_level_display": 1, + "touch_effect": 1, + "guide_sound_vol": 3, + "touch_note_vol": 8, + "hold_note_vol": 8, + "slide_note_vol": 8, + "snap_note_vol": 8, + "chain_note_vol": 8, + "bonus_note_vol": 8, + "gate_skip": 0, + "key_beam_display": 1, + + "left_slide_note_color": 4, + "right_slide_note_color": 3, + "forward_slide_note_color": 1, + "back_slide_note_color": 2, + + "master_vol": 3, + "set_title_id": 104001, + "set_icon_id": 102001, + "set_nav_id": 210001, + "set_plate_id": 211001 + } + self.allowed_stages = [] + + def handle_housing_get_request(self, data: Dict) -> Dict: + req = BaseRequest(data) + housing_id = 1337 + self.logger.info(f"{req.chipId} -> {housing_id}") + resp = HousingGetResponse(housing_id) + return resp.make() + + def handle_housing_start_request(self, data: Dict) -> Dict: + req = HousingStartRequest(data) + + resp = HousingStartResponseV1( + 1, + [ # Recomended songs + 1269,1007,1270,1002,1020,1003,1008,1211,1018,1092,1056,32, + 1260,1230,1258,1251,2212,1264,1125,1037,2001,1272,1126,1119, + 1104,1070,1047,1044,1027,1004,1001,24,2068,2062,2021,1275, + 1249,1207,1203,1107,1021,1009,9,4,3,23,22,2014,13,1276,1247, + 1240,1237,1128,1114,1110,1109,1102,1045,1043,1036,1035,1030, + 1023,1015 + ] + ) + return resp.make() + + def handle_advertise_GetNews_request(self, data: Dict) -> Dict: + resp = GetNewsResponseV1() + return resp.make() + + def handle_user_status_logout_request(self, data: Dict) -> Dict: + req = UserStatusLogoutRequest(data) + self.logger.info(f"Log out user {req.userId} from {req.chipId}") + return BaseResponse().make() + + def handle_user_status_login_request(self, data: Dict) -> List[Any]: + req = UserStatusLoginRequest(data) + resp = UserStatusLoginResponseV1() + is_new_day = False + is_consec_day = False + is_consec_day = True + + if req.userId == 0: + self.logger.info(f"Guest login on {req.chipId}") + resp.lastLoginDate = 0 + + else: + profile = self.data.profile.get_profile(req.userId) + if profile is None: + self.logger.warn(f"Unknown user id {req.userId} attempted login from {req.chipId}") + return resp.make() + + self.logger.info(f"User {req.userId} login on {req.chipId}") + last_login_time = int(profile["last_login_date"].timestamp()) + resp.lastLoginDate = last_login_time + + # If somebodies login timestamp < midnight of current day, then they are logging in for the first time today + if last_login_time < int(datetime.now().replace(hour=0,minute=0,second=0,microsecond=0).timestamp()): + is_new_day = True + is_consec_day = True + + # If somebodies login timestamp > midnight of current day + 1 day, then they broke their daily login streak + elif last_login_time > int((datetime.now().replace(hour=0,minute=0,second=0,microsecond=0) + timedelta(days=1)).timestamp()): + is_consec_day = False + # else, they are simply logging in again on the same day, and we don't need to do anything for that + + self.data.profile.session_login(req.userId, is_new_day, is_consec_day) + + resp.firstLoginDaily = int(is_new_day) + + return resp.make() + + def handle_user_status_get_request(self, data: Dict) -> List[Any]: + req = UserStatusGetRequest(data) + resp = UserStatusGetV1Response() + ver_split = req.appVersion.split(".") + + profile = self.data.profile.get_profile(aime_id=req.aimeId) + if profile is None: + self.logger.info(f"No user exists for aime id {req.aimeId}") + return resp.make() + + + self.logger.info(f"User preview for {req.aimeId} from {req.chipId}") + if profile["last_game_ver"] is None: + profile_ver_split = ver_split + resp.lastGameVersion = req.appVersion + else: + profile_ver_split = profile["last_game_ver"].split(".") + resp.lastGameVersion = profile["last_game_ver"] + + resp.userStatus.userId = profile["id"] + resp.userStatus.username = profile["username"] + resp.userStatus.xp = profile["xp"] + resp.userStatus.danLevel = profile["dan_level"] + resp.userStatus.danType = profile["dan_type"] + resp.userStatus.wp = profile["wp"] + resp.userStatus.useCount = profile["login_count"] + resp.userStatus.loginDays = profile["login_count_days"] + resp.userStatus.loginConsecutiveDays = profile["login_count_days_consec"] + + set_title_id = self.data.profile.get_options(WaccaConstants.OPTIONS["set_title_id"], profile["user"]) + if set_title_id is None: + set_title_id = self.OPTIONS_DEFAULTS["set_title_id"] + resp.setTitleId = set_title_id + + set_icon_id = self.data.profile.get_options(WaccaConstants.OPTIONS["set_title_id"], profile["user"]) + if set_icon_id is None: + set_icon_id = self.OPTIONS_DEFAULTS["set_icon_id"] + resp.setIconId = set_icon_id + + if profile["last_login_date"].timestamp() < int((datetime.now().replace(hour=0,minute=0,second=0,microsecond=0) - timedelta(days=1)).timestamp()): + resp.userStatus.loginConsecutiveDays = 0 + + if int(ver_split[0]) > int(profile_ver_split[0]): + resp.versionStatus = PlayVersionStatus.VersionUpgrade + + elif int(ver_split[0]) < int(profile_ver_split[0]): + resp.versionStatus = PlayVersionStatus.VersionTooNew + + else: + if int(ver_split[1]) > int(profile_ver_split[1]): + resp.versionStatus = PlayVersionStatus.VersionUpgrade + + elif int(ver_split[1]) < int(profile_ver_split[1]): + resp.versionStatus = PlayVersionStatus.VersionTooNew + + else: + if int(ver_split[2]) > int(profile_ver_split[2]): + resp.versionStatus = PlayVersionStatus.VersionUpgrade + + + elif int(ver_split[2]) < int(profile_ver_split[2]): + resp.versionStatus = PlayVersionStatus.VersionTooNew + + if profile["always_vip"]: + resp.userStatus.vipExpireTime = int((datetime.now() + timedelta(days=30)).timestamp()) + + elif profile["vip_expire_time"] is not None: + resp.userStatus.vipExpireTime = int(profile["vip_expire_time"].timestamp()) + + return resp.make() + + def handle_user_status_login_request(self, data: Dict) -> List[Any]: + req = UserStatusLoginRequest(data) + resp = UserStatusLoginResponseV2() + is_new_day = False + is_consec_day = False + is_consec_day = True + + if req.userId == 0: + self.logger.info(f"Guest login on {req.chipId}") + resp.lastLoginDate = 0 + + else: + profile = self.data.profile.get_profile(req.userId) + if profile is None: + self.logger.warn(f"Unknown user id {req.userId} attempted login from {req.chipId}") + return resp.make() + + self.logger.info(f"User {req.userId} login on {req.chipId}") + last_login_time = int(profile["last_login_date"].timestamp()) + resp.lastLoginDate = last_login_time + + # If somebodies login timestamp < midnight of current day, then they are logging in for the first time today + if last_login_time < int(datetime.now().replace(hour=0,minute=0,second=0,microsecond=0).timestamp()): + is_new_day = True + is_consec_day = True + + # If somebodies login timestamp > midnight of current day + 1 day, then they broke their daily login streak + elif last_login_time > int((datetime.now().replace(hour=0,minute=0,second=0,microsecond=0) + timedelta(days=1)).timestamp()): + is_consec_day = False + # else, they are simply logging in again on the same day, and we don't need to do anything for that + + self.data.profile.session_login(req.userId, is_new_day, is_consec_day) + resp.vipInfo.pageYear = datetime.now().year + resp.vipInfo.pageMonth = datetime.now().month + resp.vipInfo.pageDay = datetime.now().day + resp.vipInfo.numItem = 1 + + resp.firstLoginDaily = int(is_new_day) + + return resp.make() + + def handle_user_status_create_request(self, data: Dict) -> List[Any]: + req = UserStatusCreateRequest(data) + + profileId = self.data.profile.create_profile(req.aimeId, req.username, self.version) + + if profileId is None: return BaseResponse().make() + + # Insert starting items + self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["title"], 104001) + self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["title"], 104002) + self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["title"], 104003) + self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["title"], 104005) + + self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["icon"], 102001) + self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["icon"], 102002) + + self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["note_color"], 103001) + self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["note_color"], 203001) + + self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["note_sound"], 105001) + self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["note_sound"], 205005) # Added lily + + self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210001) + + self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["user_plate"], 211001) # Added lily + + self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["touch_effect"], 312000) # Added reverse + self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["touch_effect"], 312001) # Added reverse + self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["touch_effect"], 312002) # Added reverse + + return UserStatusCreateResponseV2(profileId, req.username).make() + + def handle_user_status_getDetail_request(self, data: Dict) -> List[Any]: + req = UserStatusGetDetailRequest(data) + resp = UserStatusGetDetailResponseV1() + + profile = self.data.profile.get_profile(req.userId) + if profile is None: + self.logger.warn(f"Unknown profile {req.userId}") + return resp.make() + + self.logger.info(f"Get detail for profile {req.userId}") + user_id = profile["user"] + + profile_scores = self.data.score.get_best_scores(user_id) + profile_items = self.data.item.get_items(user_id) + profile_song_unlocks = self.data.item.get_song_unlocks(user_id) + profile_options = self.data.profile.get_options(user_id) + profile_trophies = self.data.item.get_trophies(user_id) + profile_tickets = self.data.item.get_tickets(user_id) + + if profile["vip_expire_time"] is None: + resp.userStatus.vipExpireTime = 0 + + else: + resp.userStatus.vipExpireTime = int(profile["vip_expire_time"].timestamp()) + + if profile["always_vip"] or self.game_config.mods.always_vip: + resp.userStatus.vipExpireTime = int((self.srvtime + timedelta(days=31)).timestamp()) + + resp.songUpdateTime = int(profile["last_login_date"].timestamp()) + resp.songPlayStatus = [profile["last_song_id"], 1] + + resp.userStatus.userId = profile["id"] + resp.userStatus.username = profile["username"] + resp.userStatus.xp = profile["xp"] + resp.userStatus.danLevel = profile["dan_level"] + resp.userStatus.danType = profile["dan_type"] + resp.userStatus.wp = profile["wp"] + resp.userStatus.useCount = profile["login_count"] + resp.userStatus.loginDays = profile["login_count_days"] + resp.userStatus.loginConsecutiveDays = profile["login_count_days_consec"] + + if self.game_config.mods.infinite_wp: + resp.userStatus.wp = 999999 + + if profile["friend_view_1"] is not None: + pass + if profile["friend_view_2"] is not None: + pass + if profile["friend_view_3"] is not None: + pass + + resp.seasonalPlayModeCounts.append(PlayModeCounts(self.season, 1, profile["playcount_single"])) + resp.seasonalPlayModeCounts.append(PlayModeCounts(self.season, 2, profile["playcount_multi_vs"])) + resp.seasonalPlayModeCounts.append(PlayModeCounts(self.season, 3, profile["playcount_multi_coop"])) + resp.seasonalPlayModeCounts.append(PlayModeCounts(self.season, 4, profile["playcount_stageup"])) + + for opt in profile_options: + resp.options.append(UserOption(opt["opt_id"], opt["value"])) + + for unlock in profile_song_unlocks: + for x in range(1, unlock["highest_difficulty"] + 1): + resp.userItems.songUnlocks.append(SongUnlock(unlock["song_id"], x, 0, int(unlock["acquire_date"].timestamp()))) + if x > 2: + resp.scores.append(BestScoreDetailV1(unlock["song_id"], x)) + + empty_scores = len(resp.scores) + for song in profile_scores: + resp.seasonInfo.cumulativeScore += song["score"] + empty_score_idx = resp.find_score_idx(song["song_id"], song["chart_id"], 0, empty_scores) + + clear_cts = SongDetailClearCounts( + song["play_ct"], + song["clear_ct"], + song["missless_ct"], + song["fullcombo_ct"], + song["allmarv_ct"], + ) + + grade_cts = SongDetailGradeCountsV1( + song["grade_d_ct"], song["grade_c_ct"], song["grade_b_ct"], song["grade_a_ct"], song["grade_aa_ct"], + song["grade_aaa_ct"], song["grade_s_ct"], song["grade_ss_ct"], song["grade_sss_ct"], + song["grade_master_ct"] + ) + + if empty_score_idx is not None: + resp.scores[empty_score_idx].clearCounts = clear_cts + resp.scores[empty_score_idx].clearCountsSeason = clear_cts + resp.scores[empty_score_idx].gradeCounts = grade_cts + resp.scores[empty_score_idx].score = song["score"] + resp.scores[empty_score_idx].bestCombo = song["best_combo"] + resp.scores[empty_score_idx].lowestMissCtMaybe = song["lowest_miss_ct"] + resp.scores[empty_score_idx].rating = song["rating"] + + else: + deets = BestScoreDetailV1(song["song_id"], song["chart_id"]) + deets.clearCounts = clear_cts + deets.clearCountsSeason = clear_cts + deets.gradeCounts = grade_cts + deets.score = song["score"] + deets.bestCombo = song["best_combo"] + deets.lowestMissCtMaybe = song["lowest_miss_ct"] + deets.rating = song["rating"] + + for trophy in profile_trophies: + resp.userItems.trophies.append(TrophyItem(trophy["trophy_id"], trophy["season"], trophy["progress"], trophy["badge_type"])) + + if self.game_config.mods.infinite_tickets: + for x in range(5): + resp.userItems.tickets.append(TicketItem(x, 106002, 0)) + else: + for ticket in profile_tickets: + if ticket["expire_date"] is None: + expire = int((self.srvtime + timedelta(days=30)).timestamp()) + else: + expire = int(ticket["expire_date"].timestamp()) + + resp.userItems.tickets.append(TicketItem(ticket["id"], ticket["ticket_id"], expire)) + + if profile_items: + for item in profile_items: + try: + + if item["type"] == WaccaConstants.ITEM_TYPES["icon"]: + resp.userItems.icons.append(IconItem(item["item_id"], 1, item["use_count"], int(item["acquire_date"].timestamp()))) + + else: + itm_send = GenericItemSend(item["item_id"], 1, int(item["acquire_date"].timestamp())) + + if item["type"] == WaccaConstants.ITEM_TYPES["title"]: + resp.userItems.titles.append(itm_send) + + elif item["type"] == WaccaConstants.ITEM_TYPES["note_color"]: + resp.userItems.noteColors.append(itm_send) + + elif item["type"] == WaccaConstants.ITEM_TYPES["note_sound"]: + resp.userItems.noteSounds.append(itm_send) + + except: + self.logger.error(f"{__name__} Failed to load item {item['item_id']} for user {profile['user']}") + + resp.seasonInfo.level = profile["xp"] + resp.seasonInfo.wpObtained = profile["wp_total"] + resp.seasonInfo.wpSpent = profile["wp_spent"] + resp.seasonInfo.titlesObtained = len(resp.userItems.titles) + resp.seasonInfo.iconsObtained = len(resp.userItems.icons) + resp.seasonInfo.noteColorsObtained = len(resp.userItems.noteColors) + resp.seasonInfo.noteSoundsObtained = len(resp.userItems.noteSounds) + + return resp.make() + + def handle_user_trial_get_request(self, data: Dict) -> List[Any]: + req = UserTrialGetRequest(data) + resp = UserTrialGetResponse() + + user_id = self.data.profile.profile_to_aime_user(req.profileId) + if user_id is None: + self.logger.error(f"handle_user_trial_get_request: No profile with id {req.profileId}") + return resp.make() + + self.logger.info(f"Get trial info for user {req.profileId}") + + for d in self.allowed_stages: + if d[1] > 0 and d[1] < 10: + resp.stageList.append(StageInfo(d[0], d[1])) + + stages = self.data.score.get_stageup(user_id, self.version) + if stages is None: + stages = [] + + add_next = True + for d in self.allowed_stages: + stage_info = StageInfo(d[0], d[1]) + + for score in stages: + if score["stage_id"] == stage_info.danId: + stage_info.clearStatus = score["clear_status"] + stage_info.numSongCleared = score["clear_song_ct"] + stage_info.song1BestScore = score["song1_score"] + stage_info.song2BestScore = score["song2_score"] + stage_info.song3BestScore = score["song3_score"] + break + + if add_next or stage_info.danLevel < 9: + resp.stageList.append(stage_info) + + if stage_info.danLevel >= 9 and stage_info.clearStatus < 1: + add_next = False + + return resp.make() + + def handle_user_trial_update_request(self, data: Dict) -> List[Any]: + req = UserTrialUpdateRequest(data) + + total_score = 0 + for score in req.songScores: + total_score += score + + while len(req.songScores) < 3: + req.songScores.append(0) + + profile = self.data.profile.get_profile(req.profileId) + + user_id = profile["user"] + old_stage = self.data.score.get_stageup_stage(user_id, self.version, req.stageId) + + if old_stage is None: + self.data.score.put_stageup(user_id, self.version, req.stageId, req.clearType.value, req.numSongsCleared, req.songScores[0], req.songScores[1], req.songScores[2]) + + else: + # We only care about total score for best of, even if one score happens to be lower (I think) + if total_score > (old_stage["song1_score"] + old_stage["song2_score"] + old_stage["song3_score"]): + best_score1 = req.songScores[0] + best_score2 = req.songScores[2] + best_score3 = req.songScores[3] + else: + best_score1 = old_stage["song1_score"] + best_score2 = old_stage["song2_score"] + best_score3 = old_stage["song3_score"] + + self.data.score.put_stageup(user_id, self.version, req.stageId, req.clearType.value, req.numSongsCleared, + best_score1, best_score2, best_score3) + + if req.stageLevel > 0 and req.stageLevel <= 14 and req.clearType.value > 0: # For some reason, special stages send dan level 1001... + if req.stageLevel > profile["dan_level"] or (req.stageLevel == profile["dan_level"] and req.clearType.value > profile["dan_type"]): + self.data.profile.update_profile_dan(req.profileId, req.stageLevel, req.clearType.value) + + self.util_put_items(req.profileId, user_id, req.itemsObtained) + + # user/status/update isn't called after stageup so we have to do some things now + current_icon = self.data.profile.get_options(user_id, WaccaConstants.OPTIONS["set_icon_id"]) + current_nav = self.data.profile.get_options(user_id, WaccaConstants.OPTIONS["set_nav_id"]) + + if current_icon is None: + current_icon = self.OPTIONS_DEFAULTS["set_icon_id"] + else: + current_icon = current_icon["value"] + if current_nav is None: + current_nav = self.OPTIONS_DEFAULTS["set_nav_id"] + else: + current_nav = current_nav["value"] + + self.data.item.put_item(user_id, WaccaConstants.ITEM_TYPES["icon"], current_icon) + self.data.item.put_item(user_id, WaccaConstants.ITEM_TYPES["navigator"], current_nav) + self.data.profile.update_profile_playtype(req.profileId, 4, data["appVersion"][:7]) + return BaseResponse.make() + + def handle_user_sugoroku_update_request(self, data: Dict) -> List[Any]: + ver_split = data["appVersion"].split(".") + resp = BaseResponse() + + if int(ver_split[0]) <= 2 and int(ver_split[1]) < 53: + req = UserSugarokuUpdateRequestV1(data) + mission_flg = 0 + + else: + req = UserSugarokuUpdateRequestV2(data) + mission_flg = req.mission_flag + + user_id = self.data.profile.profile_to_aime_user(req.profileId) + if user_id is None: + self.logger.info(f"handle_user_sugoroku_update_request unknwon profile ID {req.profileId}") + return resp.make() + + self.util_put_items(req.profileId, user_id, req.itemsObtainted) + + self.data.profile.update_gate(user_id, req.gateId, req.page, req.progress, req.loops, mission_flg, req.totalPts) + return resp.make() + + def handle_user_info_getMyroom_request(self, data: Dict) -> List[Any]: + return UserInfogetMyroomResponse().make() + + def handle_user_music_unlock_request(self, data: Dict) -> List[Any]: + req = UserMusicUnlockRequest(data) + + profile = self.data.profile.get_profile(req.profileId) + if profile is None: return BaseResponse().make() + user_id = profile["user"] + current_wp = profile["wp"] + + tickets = self.data.item.get_tickets(user_id) + new_tickets = [] + + for ticket in tickets: + new_tickets.append([ticket["id"], ticket["ticket_id"], 9999999999]) + + for item in req.itemsUsed: + if item.itemType == WaccaConstants.ITEM_TYPES["wp"]: + if current_wp >= item.quantity: + current_wp -= item.quantity + self.data.profile.spend_wp(req.profileId, item.quantity) + else: return BaseResponse().make() + + elif item.itemType == WaccaConstants.ITEM_TYPES["ticket"]: + for x in range(len(new_tickets)): + if new_tickets[x][1] == item.itemId: + self.data.item.spend_ticket(new_tickets[x][0]) + new_tickets.pop(x) + break + + # wp, ticket info + if req.difficulty > WaccaConstants.Difficulty.HARD.value: + old_score = self.data.score.get_best_score(user_id, req.songId, req.difficulty) + if not old_score: + self.data.score.put_best_score(user_id, req.songId, req.difficulty, 0, [0] * 5, [0] * 13, 0, 0) + + self.data.item.unlock_song(user_id, req.songId, req.difficulty if req.difficulty > WaccaConstants.Difficulty.HARD.value else WaccaConstants.Difficulty.HARD.value) + + if self.game_config.mods.infinite_tickets: + new_tickets = [ + [0, 106002, 0], + [1, 106002, 0], + [2, 106002, 0], + [3, 106002, 0], + [4, 106002, 0], + ] + + if self.game_config.mods.infinite_wp: + current_wp = 999999 + + return UserMusicUnlockResponse(current_wp, new_tickets).make() + + def handle_user_info_getRanking_request(self, data: Dict) -> List[Any]: + # total score, high score by song, cumulative socre, stage up score, other score, WP ranking + # This likely requies calculating standings at regular intervals and caching the results + return UserInfogetRankingResponse().make() + + def handle_user_music_update_request(self, data: Dict) -> List[Any]: + req = UserMusicUpdateRequest(data) + ver_split = req.appVersion.split(".") + if int(ver_split[0]) >= 3: + resp = UserMusicUpdateResponseV3() + elif int(ver_split[0]) >= 2: + resp = UserMusicUpdateResponseV2() + else: + resp = UserMusicUpdateResponseV1() + + resp.songDetail.songId = req.songDetail.songId + resp.songDetail.difficulty = req.songDetail.difficulty + + profile = self.data.profile.get_profile(req.profileId) + + if profile is None: + self.logger.warn(f"handle_user_music_update_request: No profile for game_id {req.profileId}") + return BaseResponse().make() + + user_id = profile["user"] + self.util_put_items(req.profileId, user_id, req.itemsObtained) + + playlog_clear_status = req.songDetail.flagCleared + req.songDetail.flagMissless + req.songDetail.flagFullcombo + \ + req.songDetail.flagAllMarvelous + + self.data.score.put_playlog(user_id, req.songDetail.songId, req.songDetail.difficulty, req.songDetail.score, + playlog_clear_status, req.songDetail.grade.value, req.songDetail.maxCombo, req.songDetail.judgements.marvCt, + req.songDetail.judgements.greatCt, req.songDetail.judgements.goodCt, req.songDetail.judgements.missCt, + req.songDetail.fastCt, req.songDetail.slowCt, self.season) + + old_score = self.data.score.get_best_score(user_id, req.songDetail.songId, req.songDetail.difficulty) + + if not old_score: + grades = [0] * 13 + clears = [0] * 5 + + clears[0] = 1 + clears[1] = 1 if req.songDetail.flagCleared else 0 + clears[2] = 1 if req.songDetail.flagMissless else 0 + clears[3] = 1 if req.songDetail.flagFullcombo else 0 + clears[4] = 1 if req.songDetail.flagAllMarvelous else 0 + + grades[req.songDetail.grade.value - 1] = 1 + + self.data.score.put_best_score(user_id, req.songDetail.songId, req.songDetail.difficulty, req.songDetail.score, + clears, grades, req.songDetail.maxCombo, req.songDetail.judgements.missCt) + + resp.songDetail.score = req.songDetail.score + resp.songDetail.lowestMissCount = req.songDetail.judgements.missCt + + else: + grades = [ + old_score["grade_d_ct"], + old_score["grade_c_ct"], + old_score["grade_b_ct"], + old_score["grade_a_ct"], + old_score["grade_aa_ct"], + old_score["grade_aaa_ct"], + old_score["grade_s_ct"], + old_score["grade_ss_ct"], + old_score["grade_sss_ct"], + old_score["grade_master_ct"], + old_score["grade_sp_ct"], + old_score["grade_ssp_ct"], + old_score["grade_sssp_ct"], + ] + clears = [ + old_score["play_ct"], + old_score["clear_ct"], + old_score["missless_ct"], + old_score["fullcombo_ct"], + old_score["allmarv_ct"], + ] + + clears[0] += 1 + clears[1] += 1 if req.songDetail.flagCleared else 0 + clears[2] += 1 if req.songDetail.flagMissless else 0 + clears[3] += 1 if req.songDetail.flagFullcombo else 0 + clears[4] += 1 if req.songDetail.flagAllMarvelous else 0 + + grades[req.songDetail.grade.value - 1] += 1 + + best_score = max(req.songDetail.score, old_score["score"]) + best_max_combo = max(req.songDetail.maxCombo, old_score["best_combo"]) + lowest_miss_ct = min(req.songDetail.judgements.missCt, old_score["lowest_miss_ct"]) + best_rating = max(self.util_calc_song_rating(req.songDetail.score, req.songDetail.level), old_score["rating"]) + + self.data.score.put_best_score(user_id, req.songDetail.songId, req.songDetail.difficulty, best_score, clears, + grades, best_max_combo, lowest_miss_ct) + + resp.songDetail.score = best_score + resp.songDetail.lowestMissCount = lowest_miss_ct + resp.songDetail.rating = best_rating + + resp.songDetail.clearCounts = SongDetailClearCounts(counts=clears) + resp.songDetail.clearCountsSeason = SongDetailClearCounts(counts=clears) + + if int(ver_split[0]) >= 3: + resp.songDetail.grades = SongDetailGradeCountsV2(counts=grades) + else: + resp.songDetail.grades = SongDetailGradeCountsV1(counts=grades) + + return resp.make() + + #TODO: Coop and vs data + def handle_user_music_updateCoop_request(self, data: Dict) -> List[Any]: + coop_info = data["params"][4] + return self.handle_user_music_update_request(data) + + def handle_user_music_updateVersus_request(self, data: Dict) -> List[Any]: + vs_info = data["params"][4] + return self.handle_user_music_update_request(data) + + def handle_user_music_updateTrial_request(self, data: Dict) -> List[Any]: + return self.handle_user_music_update_request(data) + + def handle_user_mission_update_request(self, data: Dict) -> List[Any]: + req = UserMissionUpdateRequest(data) + page_status = req.params[1][1] + + profile = self.data.profile.get_profile(req.profileId) + if profile is None: + return BaseResponse().make() + + if len(req.itemsObtained) > 0: + self.util_put_items(req.profileId, profile["user"], req.itemsObtained) + + self.data.profile.update_bingo(profile["user"], req.bingoDetail.pageNumber, page_status) + self.data.profile.update_tutorial_flags(req.profileId, req.params[3]) + + return BaseResponse().make() + + def handle_user_goods_purchase_request(self, data: Dict) -> List[Any]: + req = UserGoodsPurchaseRequest(data) + resp = UserGoodsPurchaseResponse() + + profile = self.data.profile.get_profile(req.profileId) + if profile is None: return BaseResponse().make() + + user_id = profile["user"] + resp.currentWp = profile["wp"] + + if req.purchaseType == PurchaseType.PurchaseTypeWP: + resp.currentWp -= req.cost + self.data.profile.spend_wp(req.profileId, req.cost) + + elif req.purchaseType == PurchaseType.PurchaseTypeCredit: + self.logger.info(f"User {req.profileId} Purchased item {req.itemObtained.itemType} id {req.itemObtained.itemId} for {req.cost} credits on machine {req.chipId}") + + self.util_put_items(req.profileId ,user_id, [req.itemObtained]) + + if self.game_config.mods.infinite_tickets: + for x in range(5): + resp.tickets.append(TicketItem(x, 106002, 0)) + else: + tickets = self.data.item.get_tickets(user_id) + + for ticket in tickets: + resp.tickets.append(TicketItem(ticket["id"], ticket["ticket_id"], int((self.srvtime + timedelta(days=30)).timestamp()))) + + if self.game_config.mods.infinite_wp: + resp.currentWp = 999999 + + return resp.make() + + def handle_competition_status_login_request(self, data: Dict) -> List[Any]: + return BaseResponse().make() + + def handle_competition_status_update_request(self, data: Dict) -> List[Any]: + return BaseResponse().make() + + def handle_user_rating_update_request(self, data: Dict) -> List[Any]: + req = UserRatingUpdateRequest(data) + + user_id = self.data.profile.profile_to_aime_user(req.profileId) + + if user_id is None: + self.logger.error(f"handle_user_rating_update_request: No profild with ID {req.profileId}") + return BaseResponse().make() + + for song in req.songs: + self.data.score.update_song_rating(user_id, song.songId, song.difficulty, song.rating) + + self.data.profile.update_user_rating(req.profileId, req.totalRating) + + return BaseResponse().make() + + def handle_user_status_update_request(self, data: Dict) -> List[Any]: + req = UserStatusUpdateRequestV2(data) + + user_id = self.data.profile.profile_to_aime_user(req.profileId) + if user_id is None: + self.logger.info(f"handle_user_status_update_request: No profile with ID {req.profileId}") + return BaseResponse().make() + + self.util_put_items(req.profileId, user_id, req.itemsRecieved) + self.data.profile.update_profile_playtype(req.profileId, req.playType.value, data["appVersion"][:7]) + self.data.profile.update_profile_lastplayed(req.profileId, req.lastSongInfo.lastSongId, req.lastSongInfo.lastSongDiff, + req.lastSongInfo.lastFolderOrd, req.lastSongInfo.lastFolderId, req.lastSongInfo.lastSongOrd) + + current_icon = self.data.profile.get_options(user_id, WaccaConstants.OPTIONS["set_icon_id"]) + current_nav = self.data.profile.get_options(user_id, WaccaConstants.OPTIONS["set_nav_id"]) + + if current_icon is None: + current_icon = self.OPTIONS_DEFAULTS["set_icon_id"] + else: + current_icon = current_icon["value"] + if current_nav is None: + current_nav = self.OPTIONS_DEFAULTS["set_nav_id"] + else: + current_nav = current_nav["value"] + + self.data.item.put_item(user_id, WaccaConstants.ITEM_TYPES["icon"], current_icon) + self.data.item.put_item(user_id, WaccaConstants.ITEM_TYPES["navigator"], current_nav) + return BaseResponse().make() + + def handle_user_info_update_request(self, data: Dict) -> List[Any]: + req = UserInfoUpdateRequest(data) + + user_id = self.data.profile.profile_to_aime_user(req.profileId) + + for opt in req.optsUpdated: + self.data.profile.update_option(user_id, opt.id, opt.val) + + for update in req.datesUpdated: + pass + + for fav in req.favoritesAdded: + self.data.profile.add_favorite_song(user_id, fav) + + for unfav in req.favoritesRemoved: + self.data.profile.remove_favorite_song(user_id, unfav) + + return BaseResponse().make() + + def handle_user_vip_get_request(self, data: Dict) -> List[Any]: + req = UserVipGetRequest(data) + resp = UserVipGetResponse() + + profile = self.data.profile.get_profile(req.profileId) + if profile is None: + self.logger.warn(f"handle_user_vip_get_request no profile with ID {req.profileId}") + return BaseResponse().make() + + if "vip_expire_time" in profile and profile["vip_expire_time"] is not None and profile["vip_expire_time"].timestamp() > int(self.srvtime.timestamp()): + resp.vipDays = int((profile["vip_expire_time"].timestamp() - int(self.srvtime.timestamp())) / 86400) + + resp.vipDays += 30 + + resp.presents.append(VipLoginBonus(1,0,16,211025,1)) + resp.presents.append(VipLoginBonus(2,0,6,202086,1)) + resp.presents.append(VipLoginBonus(3,0,11,205008,1)) + resp.presents.append(VipLoginBonus(4,0,10,203009,1)) + resp.presents.append(VipLoginBonus(5,0,16,211026,1)) + resp.presents.append(VipLoginBonus(6,0,9,206001,1)) + + return resp.make() + + def handle_user_vip_start_request(self, data: Dict) -> List[Any]: + req = UserVipStartRequest(data) + + profile = self.data.profile.get_profile(req.profileId) + if profile is None: return BaseResponse().make() + + # This should never happen because wacca stops you from buying VIP + # if you have more then 10 days remaining, but this IS wacca we're dealing with... + if "always_vip" in profile and profile["always_vip"] or self.game_config.mods.always_vip: + return UserVipStartResponse(int((self.srvtime + timedelta(days=req.days)).timestamp())).make() + + profile["vip_expire_time"] = int((self.srvtime + timedelta(days=req.days)).timestamp()) + self.data.profile.update_vip_time(req.profileId, self.srvtime + timedelta(days=req.days)) + return UserVipStartResponse(profile["vip_expire_time"]).make() + + def util_put_items(self, profile_id: int, user_id: int, items_obtained: List[GenericItemRecv]) -> None: + if user_id is None or profile_id <= 0: + return None + + if items_obtained: + for item in items_obtained: + + if item.itemType == WaccaConstants.ITEM_TYPES["xp"]: + self.data.profile.add_xp(profile_id, item.quantity) + + elif item.itemType == WaccaConstants.ITEM_TYPES["wp"]: + self.data.profile.add_wp(profile_id, item.quantity) + + elif item.itemType == WaccaConstants.ITEM_TYPES["music_difficulty_unlock"] or item.itemType == WaccaConstants.ITEM_TYPES["music_unlock"]: + if item.quantity > WaccaConstants.Difficulty.HARD.value: + old_score = self.data.score.get_best_score(user_id, item.itemId, item.quantity) + if not old_score: + self.data.score.put_best_score(user_id, item.itemId, item.quantity, 0, [0] * 5, [0] * 13, 0, 0) + + if item.quantity == 0: + item.quantity = WaccaConstants.Difficulty.HARD.value + self.data.item.unlock_song(user_id, item.itemId, item.quantity) + + elif item.itemType == WaccaConstants.ITEM_TYPES["ticket"]: + self.data.item.add_ticket(user_id, item.itemId) + + elif item.itemType == WaccaConstants.ITEM_TYPES["trophy"]: + self.data.item.update_trophy(user_id, item.itemId, self.season, item.quantity, 0) + + else: + self.data.item.put_item(user_id, item.itemType, item.itemId) + + def util_calc_song_rating(self, score: int, difficulty: float) -> int: + if score >= 990000: + const = 4.00 + elif score >= 980000 and score < 990000: + const = 3.75 + elif score >= 970000 and score < 980000: + const = 3.50 + elif score >= 960000 and score < 970000: + const = 3.25 + elif score >= 950000 and score < 960000: + const = 3.00 + elif score >= 940000 and score < 950000: + const = 2.75 + elif score >= 930000 and score < 940000: + const = 2.50 + elif score >= 920000 and score < 930000: + const = 2.25 + elif score >= 910000 and score < 920000: + const = 2.00 + elif score >= 900000 and score < 910000: + const = 1.00 + else: const = 0.00 + + return floor((difficulty * const) * 10) diff --git a/titles/wacca/config.py b/titles/wacca/config.py new file mode 100644 index 0000000..f5bc235 --- /dev/null +++ b/titles/wacca/config.py @@ -0,0 +1,44 @@ +from typing import Dict, List +from core.config import CoreConfig + +class WaccaServerConfig(): + def __init__(self, parent_config: "WaccaConfig") -> None: + self.__config = parent_config + + @property + def enable(self) -> bool: + return CoreConfig.get_config_field(self.__config, 'wacca', 'server', 'enable', default=True) + + @property + def loglevel(self) -> int: + return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'wacca', 'server', 'loglevel', default="info")) + +class WaccaModsConfig(): + def __init__(self, parent_config: "WaccaConfig") -> None: + self.__config = parent_config + + @property + def always_vip(self) -> bool: + return CoreConfig.get_config_field(self.__config, 'wacca', 'mods', 'always_vip', default=True) + + @property + def infinite_tickets(self) -> bool: + return CoreConfig.get_config_field(self.__config, 'wacca', 'mods', 'infinite_tickets', default=True) + + @property + def infinite_wp(self) -> bool: + return CoreConfig.get_config_field(self.__config, 'wacca', 'mods', 'infinite_wp', default=True) + +class WaccaGateConfig(): + def __init__(self, parent_config: "WaccaConfig") -> None: + self.__config = parent_config + + @property + def enabled_gates(self) -> List[int]: + return CoreConfig.get_config_field(self.__config, 'wacca', 'gates', 'enabled_gates', default=[]) + +class WaccaConfig(dict): + def __init__(self) -> None: + self.server = WaccaServerConfig(self) + self.mods = WaccaModsConfig(self) + self.gates = WaccaGateConfig(self) diff --git a/titles/wacca/const.py b/titles/wacca/const.py new file mode 100644 index 0000000..910f313 --- /dev/null +++ b/titles/wacca/const.py @@ -0,0 +1,113 @@ +from enum import Enum + +class WaccaConstants(): + CONFIG_NAME = "wacca.yaml" + GAME_CODE = "SDFE" + + VER_WACCA = 0 + VER_WACCA_S = 1 + VER_WACCA_LILY = 2 + VER_WACCA_LILY_R = 3 + VER_WACCA_REVERSE = 4 + + VERSION_NAMES = ("WACCA", "WACCA S", "WACCA Lily", "WACCA Lily R", "WACCA Reverse") + + class GRADES(Enum): + D = 1 + C = 2 + B = 3 + A = 4 + AA = 5 + AAA = 6 + S = 7 + SS = 8 + SSS = 9 + MASTER = 10 + S_PLUS = 11 + SS_PLUS = 12 + SSS_PLUS = 13 + + ITEM_TYPES = { + "xp": 1, + "wp": 2, + "music_unlock": 3, + "music_difficulty_unlock": 4, + "title": 5, + "icon": 6, + "trophy": 7, + "skill": 8, + "ticket": 9, + "note_color": 10, + "note_sound": 11, + "baht_do_not_send": 12, + "boost_badge": 13, + "gate_point": 14, + "navigator": 15, + "user_plate": 16, + "touch_effect": 17, + } + + OPTIONS = { + "note_speed": 1, # 1.0 - 6.0 + "field_mask": 2, # 0-4 + "note_sound": 3, # ID + "note_color": 4, # ID + "bgm_volume": 5, # 0-100 incremements of 10 + "bg_video": 7, # ask, on, or off + + "mirror": 101, # none or left+right swap + "judge_display_pos": 102, # center, under, over, top or off + "judge_detail_display": 103, # on or off + "measure_guidelines": 105, # on or off + "guideline_mask": 106, # 0 - 5 + "judge_line_timing_adjust": 108, # -10 - 10 + "note_design": 110, # 1 - 5 + "bonus_effect": 114, # on or off + "chara_voice": 115, # "usually" or none + "score_display_method": 116, # add or subtract + "give_up": 117, # off, no touch, can't achieve s, ss, sss, pb + "guideline_spacing": 118, # none, or a-g type + "center_display": 119, # none, combo, score add, score sub, s ss sss pb boarder + "ranking_display": 120, # on or off + "stage_up_icon_display": 121, # on or off + "rating_display": 122, # on or off + "player_level_display": 123, # on or off + "touch_effect": 124, # on or off + "guide_sound_vol": 125, # 0-100 incremements of 10 + "touch_note_vol": 126, # 0-100 incremements of 10 + "hold_note_vol": 127, # 0-100 incremements of 10 + "slide_note_vol": 128, # 0-100 incremements of 10 + "snap_note_vol": 129, # 0-100 incremements of 10 + "chain_note_vol": 130, # 0-100 incremements of 10 + "bonus_note_vol": 131, # 0-100 incremements of 10 + "gate_skip": 132, # on or off + "key_beam_display": 133, # on or off + + "left_slide_note_color": 201, # red blue green or orange + "right_slide_note_color": 202, # red blue green or orange + "forward_slide_note_color": 203, # red blue green or orange + "back_slide_note_color": 204, # red blue green or orange + + "master_vol": 1001, # 0-100 incremements of 10 + "set_title_id": 1002, # ID + "set_icon_id": 1003, # ID + "set_nav_id": 1004, # ID + "set_plate_id": 1005, # ID + } + + DIFFICULTIES = { + "Normal": 1, + "Hard": 2, + "Expert": 3, + "Inferno": 4, + } + + class Difficulty(Enum): + NORMAL = 1 + HARD = 2 + EXPERT = 3 + INFERNO = 4 + + @classmethod + def game_ver_to_string(cls, ver: int): + return cls.VERSION_NAMES[ver] \ No newline at end of file diff --git a/titles/wacca/database.py b/titles/wacca/database.py new file mode 100644 index 0000000..8d4c8a5 --- /dev/null +++ b/titles/wacca/database.py @@ -0,0 +1,12 @@ +from core.data import Data +from core.config import CoreConfig +from titles.wacca.schema import * + +class WaccaData(Data): + def __init__(self, cfg: CoreConfig) -> None: + super().__init__(cfg) + + self.profile = WaccaProfileData(self.config, self.session) + self.score = WaccaScoreData(self.config, self.session) + self.item = WaccaItemData(self.config, self.session) + self.static = WaccaStaticData(self.config, self.session) \ No newline at end of file diff --git a/titles/wacca/handlers/__init__.py b/titles/wacca/handlers/__init__.py new file mode 100644 index 0000000..a59c7c1 --- /dev/null +++ b/titles/wacca/handlers/__init__.py @@ -0,0 +1,9 @@ +from titles.wacca.handlers.base import * +from titles.wacca.handlers.advertise import * +from titles.wacca.handlers.housing import * +from titles.wacca.handlers.user_info import * +from titles.wacca.handlers.user_misc import * +from titles.wacca.handlers.user_music import * +from titles.wacca.handlers.user_status import * +from titles.wacca.handlers.user_trial import * +from titles.wacca.handlers.user_vip import * \ No newline at end of file diff --git a/titles/wacca/handlers/advertise.py b/titles/wacca/handlers/advertise.py new file mode 100644 index 0000000..cf41359 --- /dev/null +++ b/titles/wacca/handlers/advertise.py @@ -0,0 +1,45 @@ +from typing import List, Dict + +from titles.wacca.handlers.base import BaseResponse +from titles.wacca.handlers.helpers import Notice + +# ---advertise/GetNews--- +class GetNewsResponseV1(BaseResponse): + def __init__(self) -> None: + super().__init__() + self.notices: list[Notice] = [] + self.copywrightListings: list[str] = [] + self.stoppedSongs: list[int] = [] + self.stoppedJackets: list[int] = [] + self.stoppedMovies: list[int] = [] + self.stoppedIcons: list[int] = [] + + def make(self) -> Dict: + note = [] + + for notice in self.notices: + note.append(notice.make()) + + self.params = [ + note, + self.copywrightListings, + self.stoppedSongs, + self.stoppedJackets, + self.stoppedMovies, + self.stoppedIcons + ] + + return super().make() + +class GetNewsResponseV2(GetNewsResponseV1): + stoppedProducts: list[int] = [] + stoppedNavs: list[int] = [] + stoppedNavVoices: list[int] = [] + + def make(self) -> Dict: + super().make() + self.params.append(self.stoppedProducts) + self.params.append(self.stoppedNavs) + self.params.append(self.stoppedNavVoices) + + return super(GetNewsResponseV1, self).make() diff --git a/titles/wacca/handlers/base.py b/titles/wacca/handlers/base.py new file mode 100644 index 0000000..1e1197b --- /dev/null +++ b/titles/wacca/handlers/base.py @@ -0,0 +1,31 @@ +from typing import Dict, List +from datetime import datetime + +class BaseRequest(): + def __init__(self, data: Dict) -> None: + self.requestNo: int = data["requestNo"] + self.appVersion: str = data["appVersion"] + self.boardId: str = data["boardId"] + self.chipId: str = data["chipId"] + self.params: List = data["params"] + +class BaseResponse(): + def __init__(self) -> None: + self.status: int = 0 + self.message: str = "" + self.serverTime: int = int(datetime.now().timestamp()) + self.maintNoticeTime: int = 0 + self.maintNotPlayableTime: int = 0 + self.maintStartTime: int = 0 + self.params: List = [] + + def make(self) -> Dict: + return { + "status": self.status, + "message": self.message, + "serverTime": self.serverTime, + "maintNoticeTime": self.maintNoticeTime, + "maintNotPlayableTime": self.maintNotPlayableTime, + "maintStartTime": self.maintStartTime, + "params": self.params + } diff --git a/titles/wacca/handlers/helpers.py b/titles/wacca/handlers/helpers.py new file mode 100644 index 0000000..19991e5 --- /dev/null +++ b/titles/wacca/handlers/helpers.py @@ -0,0 +1,786 @@ +from typing import List, Dict, Any +from enum import Enum + +from titles.wacca.const import WaccaConstants + +class HousingInfo(): + """ + 1 is lan install role, 2 is country + """ + id: int = 0 + val: str = "" + + def __init__(self, id: int = 0, val: str = "") -> None: + self.id = id + self.val = val + + def make(self) -> List: + return [ self.id, self.val ] + +class Notice(): + name: str = "" + title: str = "" + message: str = "" + unknown3: str = "" + unknown4: str = "" + showTitleScreen: bool = True + showWelcomeScreen: bool = True + startTime: int = 0 + endTime: int = 0 + voiceline: int = 0 + + def __init__(self, name: str = "", title: str = "", message: str = "", start: int = 0, end: int = 0) -> None: + self.name = name + self.title = title + self.message = message + self.startTime = start + self.endTime = end + + def make(self) -> List: + return [ self.name, self.title, self.message, self.unknown3, self.unknown4, int(self.showTitleScreen), + int(self.showWelcomeScreen), self.startTime, self.endTime, self.voiceline] + +class UserOption(): + opt_id: int + opt_val: Any + + def __init__(self, opt_id: int = 0, opt_val: Any = 0) -> None: + self.opt_id = opt_id + self.opt_val = opt_val + + def make(self) -> List: + return [self.opt_id, self.opt_val] + +class UserStatusV1(): + def __init__(self) -> None: + self.userId: int = -1 + self.username: str = "" + self.userType: int = 1 + self.xp: int = 0 + self.danLevel: int = 0 + self.danType: int = 0 + self.wp: int = 0 + self.titlePartIds: List[int] = [0, 0, 0] + self.useCount: int = 0 + self.loginDays: int = 0 + self.loginConsecutive: int = 0 + self.loginConsecutiveDays: int = 0 + self.vipExpireTime: int = 0 + + def make(self) -> List: + return [ + self.userId, + self.username, + self.userType, + self.xp, + self.danLevel, + self.danType, + self.wp, + self.titlePartIds, + self.useCount, + self.loginDays, + self.loginConsecutive, + self.loginConsecutiveDays, + self.vipExpireTime + ] + +class UserStatusV2(UserStatusV1): + def __init__(self) -> None: + super().__init__() + self.loginsToday: int = 0 + self.rating: int = 0 + + def make(self) -> List: + ret = super().make() + + ret.append(self.loginsToday) + ret.append(self.rating) + + return ret + +class ProfileStatus(Enum): + ProfileGood = 0 + ProfileRegister = 1 + ProfileInUse = 2 + ProfileWrongRegion = 3 + +class PlayVersionStatus(Enum): + VersionGood = 0 + VersionTooNew = 1 + VersionUpgrade = 2 + +class PlayModeCounts(): + seasonId: int = 0 + modeId: int = 0 + playNum: int = 0 + + def __init__(self, seasonId: int, modeId: int, playNum: int) -> None: + self.seasonId = seasonId + self.modeId = modeId + self.playNum = playNum + + def make(self) -> List: + return [ + self.seasonId, + self.modeId, + self.playNum + ] + +class SongUnlock(): + songId: int = 0 + difficulty: int = 0 + whenAppeared: int = 0 + whenUnlocked: int = 0 + + def __init__(self, song_id: int = 0, difficulty: int = 1, whenAppered: int = 0, whenUnlocked: int = 0) -> None: + self.songId = song_id + self.difficulty = difficulty + self.whenAppeared = whenAppered + self.whenUnlocked = whenUnlocked + + def make(self) -> List: + return [ + self.songId, + self.difficulty, + self.whenAppeared, + self.whenUnlocked + ] + +class GenericItemRecv(): + def __init__(self, item_type: int = 1, item_id: int = 1, quantity: int = 1) -> None: + self.itemId = item_id + self.itemType = item_type + self.quantity = quantity + + def make(self) -> List: + return [ self.itemType, self.itemId, self.quantity ] + +class GenericItemSend(): + def __init__(self, itemId: int, itemType: int, whenAcquired: int) -> None: + self.itemId = itemId + self.itemType = itemType + self.whenAcquired = whenAcquired + + def make(self) -> List: + return [ + self.itemId, + self.itemType, + self.whenAcquired + ] + +class IconItem(GenericItemSend): + uses: int = 0 + + def __init__(self, itemId: int, itemType: int, uses: int, whenAcquired: int) -> None: + super().__init__(itemId, itemType, whenAcquired) + self.uses = uses + + def make(self) -> List: + return [ + self.itemId, + self.itemType, + self.uses, + self.whenAcquired + ] + +class TrophyItem(): + trophyId: int = 0 + season: int = 1 + progress: int = 0 + badgeType: int = 0 + + def __init__(self, trophyId: int, season: int, progress: int, badgeType: int) -> None: + self.trophyId = trophyId + self.season = season + self.progress = progress + self.badgeType = badgeType + + def make(self) -> List: + return [ + self.trophyId, + self.season, + self.progress, + self.badgeType + ] + +class TicketItem(): + userTicketId: int = 0 + ticketId: int = 0 + whenExpires: int = 0 + + def __init__(self, userTicketId: int, ticketId: int, whenExpires: int) -> None: + self.userTicketId = userTicketId + self.ticketId = ticketId + self.whenExpires = whenExpires + + def make(self) -> List: + return [ + self.userTicketId, + self.ticketId, + self.whenExpires + ] + +class NavigatorItem(IconItem): + usesToday: int = 0 + + def __init__(self, itemId: int, itemType: int, whenAcquired: int, uses: int, usesToday: int) -> None: + super().__init__(itemId, itemType, uses, whenAcquired) + self.usesToday = usesToday + + def make(self) -> List: + return [ + self.itemId, + self.itemType, + self.whenAcquired, + self.uses, + self.usesToday + ] + +class SkillItem(): + skill_type: int + level: int + flag: int + badge: int + + def make(self) -> List: + return [ + self.skill_type, + self.level, + self.flag, + self.badge + ] + +class UserItemInfoV1(): + def __init__(self) -> None: + self.songUnlocks: List[SongUnlock] = [] + self.titles: List[GenericItemSend] = [] + self.icons: List[IconItem] = [] + self.trophies: List[TrophyItem] = [] + self.skills: List[SkillItem] = [] + self.tickets: List[TicketItem] = [] + self.noteColors: List[GenericItemSend] = [] + self.noteSounds: List[GenericItemSend] = [] + + def make(self) -> List: + unlocks = [] + titles = [] + icons = [] + trophies = [] + skills = [] + tickets = [] + colors = [] + sounds = [] + + for x in self.songUnlocks: + unlocks.append(x.make()) + for x in self.titles: + titles.append(x.make()) + for x in self.icons: + icons.append(x.make()) + for x in self.trophies: + trophies.append(x.make()) + for x in self.skills: + skills.append(x.make()) + for x in self.tickets: + tickets.append(x.make()) + for x in self.noteColors: + colors.append(x.make()) + for x in self.noteSounds: + sounds.append(x.make()) + + return [ + unlocks, + titles, + icons, + trophies, + skills, + tickets, + colors, + sounds, + ] + +class UserItemInfoV2(UserItemInfoV1): + def __init__(self) -> None: + super().__init__() + self.navigators: List[NavigatorItem] = [] + self.plates: List[GenericItemSend] = [] + + def make(self) -> List: + ret = super().make() + plates = [] + navs = [] + + for x in self.navigators: + navs.append(x.make()) + for x in self.plates: + plates.append(x.make()) + + ret.append(navs) + ret.append(plates) + return ret + +class UserItemInfoV3(UserItemInfoV2): + def __init__(self) -> None: + super().__init__() + self.touchEffect: List[GenericItemSend] = [] + + def make(self) -> List: + ret = super().make() + effect = [] + + for x in self.touchEffect: + effect.append(x.make()) + + ret.append(effect) + return ret + +class SongDetailClearCounts(): + def __init__(self, play_ct: int = 0, clear_ct: int = 0, ml_ct: int = 0, fc_ct: int = 0, + am_ct: int = 0, counts: List[int] = None) -> None: + if counts is None: + self.playCt = play_ct + self.clearCt = clear_ct + self.misslessCt = ml_ct + self.fullComboCt = fc_ct + self.allMarvelousCt = am_ct + + else: + self.playCt = counts[0] + self.clearCt = counts[1] + self.misslessCt = counts[2] + self.fullComboCt = counts[3] + self.allMarvelousCt = counts[4] + + def make(self) -> List: + return [self.playCt, self.clearCt, self.misslessCt, self.fullComboCt, self.allMarvelousCt] + +class SongDetailGradeCountsV1(): + dCt: int + cCt: int + bCt: int + aCt: int + aaCt: int + aaaCt: int + sCt: int + ssCt: int + sssCt: int + masterCt: int + + def __init__(self, d: int = 0, c: int = 0, b: int = 0, a: int = 0, aa: int = 0, aaa: int = 0, s: int = 0, + ss: int = 0, sss: int = 0, master: int = 0, counts: List[int] = None) -> None: + if counts is None: + self.dCt = d + self.cCt = c + self.bCt = b + self.aCt = a + self.aaCt = aa + self.aaaCt = aaa + self.sCt = s + self.ssCt = ss + self.sssCt = sss + self.masterCt = master + + else: + self.dCt = counts[0] + self.cCt = counts[1] + self.bCt = counts[2] + self.aCt = counts[3] + self.aaCt = counts[4] + self.aaaCt = counts[5] + self.sCt = counts[6] + self.ssCt = counts[7] + self.sssCt = counts[8] + self.masterCt =counts[9] + + def make(self) -> List: + return [self.dCt, self.cCt, self.bCt, self.aCt, self.aaCt, self.aaaCt, self.sCt, self.ssCt, self.sssCt, self.masterCt] + +class SongDetailGradeCountsV2(SongDetailGradeCountsV1): + spCt: int + sspCt: int + ssspCt: int + + def __init__(self, d: int = 0, c: int = 0, b: int = 0, a: int = 0, aa: int = 0, aaa: int = 0, s: int = 0, + ss: int = 0, sss: int = 0, master: int = 0, sp: int = 0, ssp: int = 0, sssp: int = 0, counts: List[int] = None, ) -> None: + super().__init__(d, c, b, a, aa, aaa, s, ss, sss, master, counts) + if counts is None: + self.spCt = sp + self.sspCt = ssp + self.ssspCt = sssp + + else: + self.spCt = counts[10] + self.sspCt = counts[11] + self.ssspCt = counts[12] + + def make(self) -> List: + return super().make() + [self.spCt, self.sspCt, self.ssspCt] + +class BestScoreDetailV1(): + songId: int = 0 + difficulty: int = 1 + clearCounts: SongDetailClearCounts = SongDetailClearCounts() + clearCountsSeason: SongDetailClearCounts = SongDetailClearCounts() + gradeCounts: SongDetailGradeCountsV1 = SongDetailGradeCountsV1() + score: int = 0 + bestCombo: int = 0 + lowestMissCtMaybe: int = 0 + isUnlock: int = 1 + rating: int = 0 + + def __init__(self, song_id: int, difficulty: int = 1) -> None: + self.songId = song_id + self.difficulty = difficulty + + def make(self) -> List: + return [ + self.songId, + self.difficulty, + self.clearCounts.make(), + self.clearCountsSeason.make(), + self.gradeCounts.make(), + self.score, + self.bestCombo, + self.lowestMissCtMaybe, + self.isUnlock, + self.rating + ] + +class BestScoreDetailV2(BestScoreDetailV1): + gradeCounts: SongDetailGradeCountsV2 = SongDetailGradeCountsV2() + +class SongUpdateJudgementCounts(): + marvCt: int + greatCt: int + goodCt: int + missCt: int + + def __init__(self, marvs: int = 0, greats: int = 0, goods: int = 0, misses: int = 0) -> None: + self.marvCt = marvs + self.greatCt = greats + self.goodCt = goods + self.missCt = misses + + def make(self) -> List: + return [self.marvCt, self.greatCt, self.goodCt, self.missCt] + +class SongUpdateDetail(): + songId: int + difficulty: int + level: float + score: int + judgements: SongUpdateJudgementCounts + maxCombo: int + grade: WaccaConstants.GRADES + flagCleared: bool + flagMissless: bool + flagFullcombo: bool + flagAllMarvelous: bool + flagGiveUp: bool + skillPt: int + fastCt: int + slowCt: int + flagNewRecord: bool + + def __init__(self, data: List = None) -> None: + if data is not None: + self.songId = data[0] + self.difficulty = data[1] + self.level = data[2] + self.score = data[3] + + self.judgements = SongUpdateJudgementCounts(data[4][0], data[4][1], data[4][2], data[4][3]) + self.maxCombo = data[5] + self.grade = WaccaConstants.GRADES(data[6]) # .value to get number, .name to get letter + + self.flagCleared = False if data[7] == 0 else True + self.flagMissless = False if data[8] == 0 else True + self.flagFullcombo = False if data[9] == 0 else True + self.flagAllMarvelous = False if data[10] == 0 else True + self.flagGiveUp = False if data[11] == 0 else True + + self.skillPt = data[12] + self.fastCt = data[13] + self.slowCt = data[14] + self.flagNewRecord = False if data[15] == 0 else True + +class SeasonalInfoV1(): + def __init__(self) -> None: + self.level: int = 0 + self.wpObtained: int = 0 + self.wpSpent: int = 0 + self.cumulativeScore: int = 0 + self.titlesObtained: int = 0 + self.iconsObtained: int = 0 + self.skillPts: int = 0 + self.noteColorsObtained: int = 0 + self.noteSoundsObtained: int = 0 + + def make(self) -> List: + return [ + self.level, + self.wpObtained, + self.wpSpent, + self.cumulativeScore, + self.titlesObtained, + self.iconsObtained, + self.skillPts, + self.noteColorsObtained, + self.noteSoundsObtained + ] + +class SeasonalInfoV2(SeasonalInfoV1): + def __init__(self) -> None: + super().__init__() + self.platesObtained: int = 0 + self.cumulativeGatePts: int = 0 + + def make(self) -> List: + return super().make() + [self.platesObtained, self.cumulativeGatePts] + +class BingoPageStatus(): + id = 0 + location = 1 + progress = 0 + + def __init__(self, id: int = 0, location: int = 1, progress: int = 0) -> None: + self.id = id + self.location = location + self.progress = progress + + def make(self) -> List: + return [self.id, self.location, self.progress] + +class BingoDetail(): + def __init__(self, pageNumber: int) -> None: + self.pageNumber = pageNumber + self.pageStatus: List[BingoPageStatus] = [] + + def make(self) -> List: + status = [] + for x in self.pageStatus: + status.append(x.make()) + + return [ + self.pageNumber, + status + ] + +class GateDetailV1(): + def __init__(self, gate_id: int = 1, page: int = 1, progress: int = 0, loops: int = 0, last_used: int = 0, mission_flg = 0) -> None: + self.id = gate_id + self.page = page + self.progress = progress + self.loops = loops + self.lastUsed = last_used + self.missionFlg = mission_flg + + def make(self) -> List: + return [self.id, 1, self.page, self.progress, self.loops, self.lastUsed] + +class GateDetailV2(GateDetailV1): + def make(self) -> List: + return super().make() + [self.missionFlg] + +class GachaInfo(): + def make() -> List: + return [] + +class LastSongDetail(): + lastSongId = 90 + lastSongDiff = 1 + lastFolderOrd = 1 + lastFolderId = 1 + lastSongOrd = 1 + + def __init__(self, last_song: int = 90, last_diff: int = 1, last_folder_ord: int = 1, + last_folder_id: int = 1, last_song_ord: int = 1) -> None: + self.lastSongId = last_song + self.lastSongDiff = last_diff + self.lastFolderOrd = last_folder_ord + self.lastFolderId = last_folder_id + self.lastSongOrd = last_song_ord + + def make(self) -> List: + return [self.lastSongId, self.lastSongDiff, self.lastFolderOrd, self.lastFolderId, + self.lastSongOrd] + +class FriendDetail(): + def make(self) -> List: + return [] + +class UserOption(): + id = 1 + val = 1 + + def __init__(self, id: int = 1, val: int = val) -> None: + self.id = id + self.val = val + + def make(self) -> List: + return [self.id, self.val] + +class LoginBonusInfo(): + def __init__(self) -> None: + self.tickets: List[TicketItem] = [] + self.items: List[GenericItemRecv] = [] + self.message: str = "" + + def make(self) -> List: + tks = [] + itms = [] + + for ticket in self.tickets: + tks.append(ticket.make()) + + for item in self.items: + itms.append(item.make()) + + return [ tks, itms, self.message ] + +class VipLoginBonus(): + id = 1 + unknown = 0 + item: GenericItemRecv + + def __init__(self, id: int = 1, unk: int = 0, item_type: int = 1, item_id: int = 1, item_qt: int = 1) -> None: + self.id = id + self.unknown = unk + self.item = GenericItemRecv(item_type, item_id, item_qt) + + def make(self) -> List: + return [ self.id, self.unknown, self.item.make() ] + +class VipInfo(): + def __init__(self, year: int = 2019, month: int = 1, day: int = 1, num_item: int = 1) -> None: + self.pageYear = year + self.pageMonth = month + self.pageDay = day + self.numItem = num_item + self.presentInfo: List[LoginBonusInfo] = [] + self.vipLoginBonus: List[VipLoginBonus] = [] + + def make(self) -> List: + pres = [] + vipBonus = [] + + for present in self.presentInfo: + pres.append(present.make()) + + for b in self.vipLoginBonus: + vipBonus.append(b.make()) + + return [ self.pageYear, self.pageMonth, self.pageDay, self.numItem, pres, vipBonus ] + +class PurchaseType(Enum): + PurchaseTypeCredit = 1 + PurchaseTypeWP = 2 + +class PlayType(Enum): + PlayTypeSingle = 1 + PlayTypeVs = 2 + PlayTypeCoop = 3 + PlayTypeStageup = 4 + +class SongRatingUpdate(): + song_id = 0 + difficulty = 0 + rating = 0 + + def __init__(self, song: int = 0, difficulty: int = 0, rating: int = 0) -> None: + self.song_id = song + self.difficulty = difficulty + self.rating = rating + + def make(self) -> List: + return [self.song_id, self.difficulty, self.rating] + +class StageInfo(): + danId: int = 0 + danLevel: int = 0 + clearStatus: int = 0 + numSongCleared: int = 0 + song1BestScore: int = 0 + song2BestScore: int = 0 + song3BestScore: int = 0 + unk5: int = 1 + + def __init__(self, dan_id: int = 0, dan_level: int = 0) -> None: + self.danId = dan_id + self.danLevel = dan_level + + def make(self) -> List: + return [ + self.danId, + self.danLevel, + self.clearStatus, + self.numSongCleared, + [ + self.song1BestScore, + self.song2BestScore, + self.song3BestScore, + ], + self.unk5 + ] + +class StageupClearType(Enum): + FAIL = 0 + CLEAR_BLUE = 1 + CLEAR_SILVER = 2 + CLEAR_GOLD = 3 + +class MusicUpdateDetailV1(): + def __init__(self) -> None: + self.songId = 0 + self.difficulty = 1 + self.clearCounts: SongDetailClearCounts = SongDetailClearCounts() + self.clearCountsSeason: SongDetailClearCounts = SongDetailClearCounts() + self.grades: SongDetailGradeCountsV1 = SongDetailGradeCountsV1() + self.score = 0 + self.lowestMissCount = 0 + self.maxSkillPts = 0 + self.locked = 0 + self.rating = 0 + + def make(self) -> List: + return [ + self.songId, + self.difficulty, + self.clearCounts.make(), + self.clearCountsSeason.make(), + self.grades.make(), + self.score, + self.lowestMissCount, + self.maxSkillPts, + self.locked, + self.rating + ] + +class MusicUpdateDetailV2(MusicUpdateDetailV1): + def __init__(self) -> None: + super().__init__() + self.grades = SongDetailGradeCountsV2() + +class SongRatingUpdate(): + def __init__(self, song_id: int = 0, difficulty: int = 1, new_rating: int = 0) -> None: + self.songId = song_id + self.difficulty = difficulty + self.rating = new_rating + + def make(self) -> List: + return [ + self.songId, + self.difficulty, + self.rating, + ] + +class GateTutorialFlag(): + def __init__(self, tutorial_id: int = 1, flg_watched: bool = False) -> None: + self.tutorialId = tutorial_id + self.flagWatched = flg_watched + + def make(self) -> List: + return [ + self.tutorialId, + int(self.flagWatched) + ] diff --git a/titles/wacca/handlers/housing.py b/titles/wacca/handlers/housing.py new file mode 100644 index 0000000..7806632 --- /dev/null +++ b/titles/wacca/handlers/housing.py @@ -0,0 +1,38 @@ +from typing import List, Dict + +from titles.wacca.handlers.base import BaseRequest, BaseResponse +from titles.wacca.handlers.helpers import HousingInfo + +# ---housing/get---- +class HousingGetResponse(BaseResponse): + def __init__(self, housingId: int) -> None: + super().__init__() + self.housingId: int = housingId + self.regionId: int = 0 + + def make(self) -> Dict: + self.params = [self.housingId, self.regionId] + return super().make() + +# ---housing/start---- +class HousingStartRequest(BaseRequest): + def __init__(self, data: Dict) -> None: + super().__init__(data) + self.unknown0: str = self.params[0] + self.errorLog: str = self.params[1] + self.unknown2: str = self.params[2] + self.info: List[HousingInfo] = [] + + for info in self.params[3]: + self.info.append(HousingInfo(info[0], info[1])) + +class HousingStartResponseV1(BaseResponse): + def __init__(self, regionId: int, songList: List[int]) -> None: + super().__init__() + self.regionId = regionId + self.songList = songList + + def make(self) -> Dict: + self.params = [self.regionId, self.songList] + + return super().make() diff --git a/titles/wacca/handlers/user_info.py b/titles/wacca/handlers/user_info.py new file mode 100644 index 0000000..c7336d7 --- /dev/null +++ b/titles/wacca/handlers/user_info.py @@ -0,0 +1,61 @@ +from typing import List, Dict + +from titles.wacca.handlers.base import BaseRequest, BaseResponse +from titles.wacca.handlers.helpers import UserOption + +# ---user/info/update--- +class UserInfoUpdateRequest(BaseRequest): + def __init__(self, data: Dict) -> None: + super().__init__(data) + self.profileId = int(self.params[0]) + self.optsUpdated: List[UserOption] = [] + self.datesUpdated: List = self.params[3] + self.favoritesAdded: List[int] = self.params[4] + self.favoritesRemoved: List[int] = self.params[5] + + for x in self.params[2]: + self.optsUpdated.append(UserOption(x[0], x[1])) + +# ---user/info/getMyroom--- TODO: Understand this better +class UserInfogetMyroomRequest(BaseRequest): + game_id = 0 + def __init__(self, data: Dict) -> None: + super().__init__(data) + self.game_id = int(self.params[0]) + +class UserInfogetMyroomResponse(BaseResponse): + def make(self) -> Dict: + self.params = [ + 0,0,0,0,0,[],0,0,0 + ] + + return super().make() + +# ---user/info/getRanking--- +class UserInfogetRankingRequest(BaseRequest): + game_id = 0 + def __init__(self, data: Dict) -> None: + super().__init__(data) + self.game_id = int(self.params[0]) + +class UserInfogetRankingResponse(BaseResponse): + def __init__(self) -> None: + super().__init__() + self.total_score_rank = 0 + self.high_score_by_song_rank = 0 + self.cumulative_score_rank = 0 + self.state_up_score_rank = 0 + self.other_score_ranking = 0 + self.wacca_points_ranking = 0 + + def make(self) -> Dict: + self.params = [ + self.total_score_rank, + self.high_score_by_song_rank, + self.cumulative_score_rank, + self.state_up_score_rank, + self.other_score_ranking, + self.wacca_points_ranking, + ] + + return super().make() \ No newline at end of file diff --git a/titles/wacca/handlers/user_misc.py b/titles/wacca/handlers/user_misc.py new file mode 100644 index 0000000..e710a8b --- /dev/null +++ b/titles/wacca/handlers/user_misc.py @@ -0,0 +1,85 @@ +from typing import List, Dict + +from titles.wacca.handlers.base import BaseRequest, BaseResponse +from titles.wacca.handlers.helpers import PurchaseType, GenericItemRecv +from titles.wacca.handlers.helpers import TicketItem, SongRatingUpdate, BingoDetail +from titles.wacca.handlers.helpers import BingoPageStatus, GateTutorialFlag + +# ---user/goods/purchase--- +class UserGoodsPurchaseRequest(BaseRequest): + def __init__(self, data: Dict) -> None: + super().__init__(data) + self.profileId = int(self.params[0]) + self.purchaseId = int(self.params[1]) + self.purchaseCount = int(self.params[2]) + self.purchaseType = PurchaseType(self.params[3]) + self.cost = int(self.params[4]) + self.itemObtained: GenericItemRecv = GenericItemRecv(self.params[5][0], self.params[5][1], self.params[5][2]) + +class UserGoodsPurchaseResponse(BaseResponse): + def __init__(self, wp: int = 0, tickets: List = []) -> None: + super().__init__() + self.currentWp = wp + self.tickets: List[TicketItem] = [] + + for ticket in tickets: + self.tickets.append(TicketItem(ticket[0], ticket[1], ticket[2])) + + def make(self) -> List: + tix = [] + for ticket in self.tickets: + tix.append(ticket.make()) + + self.params = [self.currentWp, tix] + + return super().make() + +# ---user/sugaroku/update--- +class UserSugarokuUpdateRequestV1(BaseRequest): + def __init__(self, data: Dict) -> None: + super().__init__(data) + self.profileId = int(self.params[0]) + self.gateId = int(self.params[1]) + self.page = int(self.params[2]) + self.progress = int(self.params[3]) + self.loops = int(self.params[4]) + self.boostsUsed = self.params[5] + self.totalPts = int(self.params[7]) + self.itemsObtainted: List[GenericItemRecv] = [] + + for item in self.params[6]: + self.itemsObtainted.append(GenericItemRecv(item[0], item[1], item[2])) + +class UserSugarokuUpdateRequestV2(UserSugarokuUpdateRequestV1): + def __init__(self, data: Dict) -> None: + super().__init__(data) + self.mission_flag = int(self.params[8]) + +# ---user/rating/update--- +class UserRatingUpdateRequest(BaseRequest): + def __init__(self, data: Dict) -> None: + super().__init__(data) + self.profileId = self.params[0] + self.totalRating = self.params[1] + self.songs: List[SongRatingUpdate] = [] + + for x in self.params[2]: + self.songs.append(SongRatingUpdate(x[0], x[1], x[2])) + +# ---user/mission/update--- +class UserMissionUpdateRequest(BaseRequest): + def __init__(self, data: Dict) -> None: + super().__init__(data) + self.profileId = self.params[0] + self.bingoDetail = BingoDetail(self.params[1][0]) + self.itemsObtained: List[GenericItemRecv] = [] + self.gateTutorialFlags: List[GateTutorialFlag] = [] + + for x in self.params[1][1]: + self.bingoDetail.pageStatus.append(BingoPageStatus(x[0], x[1], x[2])) + + for x in self.params[2]: + self.itemsObtained.append(GenericItemRecv(x[0], x[1], x[2])) + + for x in self.params[3]: + self.gateTutorialFlags.append(GateTutorialFlag(x[0], x[1])) diff --git a/titles/wacca/handlers/user_music.py b/titles/wacca/handlers/user_music.py new file mode 100644 index 0000000..adb11b6 --- /dev/null +++ b/titles/wacca/handlers/user_music.py @@ -0,0 +1,92 @@ +from typing import List, Dict + +from titles.wacca.handlers.base import BaseRequest, BaseResponse +from titles.wacca.handlers.helpers import GenericItemRecv, SongUpdateDetail, TicketItem +from titles.wacca.handlers.helpers import MusicUpdateDetailV1, MusicUpdateDetailV2 +from titles.wacca.handlers.helpers import SeasonalInfoV2, SeasonalInfoV1 + +# ---user/music/update--- +class UserMusicUpdateRequest(BaseRequest): + def __init__(self, data: Dict) -> None: + super().__init__(data) + self.profileId: int = self.params[0] + self.songNumber: int = self.params[1] + self.songDetail = SongUpdateDetail(self.params[2]) + self.itemsObtained: List[GenericItemRecv] = [] + + for itm in data["params"][3]: + self.itemsObtained.append(GenericItemRecv(itm[0], itm[1], itm[2])) + +class UserMusicUpdateResponseV1(BaseResponse): + def __init__(self) -> None: + super().__init__() + self.songDetail = MusicUpdateDetailV1() + self.seasonInfo = SeasonalInfoV1() + self.rankingInfo: List[List[int]] = [] + + def make(self) -> Dict: + self.params = [ + self.songDetail.make(), + [self.songDetail.songId, self.songDetail.clearCounts.playCt], + self.seasonInfo.make(), + self.rankingInfo + ] + + return super().make() + +class UserMusicUpdateResponseV2(UserMusicUpdateResponseV1): + def __init__(self) -> None: + super().__init__() + self.seasonInfo = SeasonalInfoV2() + +class UserMusicUpdateResponseV3(UserMusicUpdateResponseV2): + def __init__(self) -> None: + super().__init__() + self.songDetail = MusicUpdateDetailV2() + +# ---user/music/updateCoop--- +class UserMusicUpdateCoopRequest(UserMusicUpdateRequest): + def __init__(self, data: Dict) -> None: + super().__init__(data) + self.coopData = self.params[4] + +# ---user/music/updateVs--- +class UserMusicUpdateVsRequest(UserMusicUpdateRequest): + def __init__(self, data: Dict) -> None: + super().__init__(data) + self.vsData = self.params[4] + +# ---user/music/unlock--- +class UserMusicUnlockRequest(BaseRequest): + def __init__(self, data: Dict) -> None: + super().__init__(data) + self.profileId = self.params[0] + self.songId = self.params[1] + self.difficulty = self.params[2] + self.itemsUsed: List[GenericItemRecv] = [] + + for itm in self.params[3]: + self.itemsUsed.append(GenericItemRecv(itm[0], itm[1], itm[2])) + +class UserMusicUnlockResponse(BaseResponse): + def __init__(self, current_wp: int = 0, tickets_remaining: List = []) -> None: + super().__init__() + self.wp = current_wp + self.tickets: List[TicketItem] = [] + + for ticket in tickets_remaining: + self.tickets.append(TicketItem(ticket[0], ticket[1], ticket[2])) + + def make(self) -> List: + tickets = [] + + for ticket in self.tickets: + tickets.append(ticket.make()) + + self.params = [ + self.wp, + tickets + ] + + return super().make() + diff --git a/titles/wacca/handlers/user_status.py b/titles/wacca/handlers/user_status.py new file mode 100644 index 0000000..66d0bae --- /dev/null +++ b/titles/wacca/handlers/user_status.py @@ -0,0 +1,289 @@ +from typing import List, Dict, Optional + +from titles.wacca.handlers.base import BaseRequest, BaseResponse +from titles.wacca.handlers.helpers import * + +# ---user/status/get---- +class UserStatusGetRequest(BaseRequest): + aimeId: int = 0 + + def __init__(self, data: Dict) -> None: + super().__init__(data) + self.aimeId = int(data["params"][0]) + +class UserStatusGetV1Response(BaseResponse): + def __init__(self) -> None: + super().__init__() + self.userStatus: UserStatusV1 = UserStatusV1() + self.setTitleId: int = 0 + self.setIconId: int = 0 + self.profileStatus: ProfileStatus = ProfileStatus.ProfileGood + self.versionStatus: PlayVersionStatus = PlayVersionStatus.VersionGood + self.lastGameVersion: str = "" + + def make(self) -> Dict: + self.params = [ + self.userStatus.make(), + self.setTitleId, + self.setIconId, + self.profileStatus.value, + [ + self.versionStatus.value, + self.lastGameVersion + ] + ] + + return super().make() + +class UserStatusGetV2Response(UserStatusGetV1Response): + def __init__(self) -> None: + super().__init__() + self.userStatus: UserStatusV2 = UserStatusV2() + self.unknownArr: List = [] + + def make(self) -> Dict: + super().make() + + self.params.append(self.unknownArr) + + return super(UserStatusGetV1Response, self).make() + +# ---user/status/getDetail---- +class UserStatusGetDetailRequest(BaseRequest): + userId: int = 0 + + def __init__(self, data: Dict) -> None: + super().__init__(data) + self.userId = data["params"][0] + +class UserStatusGetDetailResponseV1(BaseResponse): + def __init__(self) -> None: + super().__init__() + self.userStatus: UserStatusV1 = UserStatusV1() + self.options: List[UserOption] = [] + self.seasonalPlayModeCounts: List[PlayModeCounts] = [] + self.userItems: UserItemInfoV1 = UserItemInfoV1() + self.scores: List[BestScoreDetailV1] = [] + self.songPlayStatus: List[int] = [0,0] + self.seasonInfo: SeasonalInfoV1 = [] + self.playAreaList: List = [ [0],[0,0,0,0,0,0],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0],[0,0,0,0],[0,0,0,0,0,0,0],[0] ] + self.songUpdateTime: int = 0 + + def make(self) -> List: + opts = [] + play_modes = [] + scores = [] + + for x in self.seasonalPlayModeCounts: + play_modes.append(x.make()) + + for x in self.scores: + scores.append(x.make()) + + for x in self.options: + opts.append(x.make()) + + self.params = [ + self.userStatus.make(), + opts, + play_modes, + self.userItems.make(), + scores, + self.songPlayStatus, + self.seasonInfo.make(), + self.playAreaList, + self.songUpdateTime + ] + + return super().make() + + def find_score_idx(self, song_id: int, difficulty: int = 1, start_idx: int = 0, stop_idx: int = None) -> Optional[int]: + if stop_idx is None or stop_idx > len(self.scores): + stop_idx = len(self.scores) + + for x in range(start_idx, stop_idx): + if self.scores[x].songId == song_id and self.scores[x].difficulty == difficulty: + return x + + return None + +class UserStatusGetDetailResponseV2(UserStatusGetDetailResponseV1): + def __init__(self) -> None: + super().__init__() + self.userStatus: UserStatusV2 = UserStatusV2() + self.seasonInfo: SeasonalInfoV2 = SeasonalInfoV2() + self.userItems: UserItemInfoV2 = UserItemInfoV2() + self.favorites: List[int] = [] + self.stoppedSongIds: List[int] = [] + self.eventInfo: List[int] = [] + self.gateInfo: List[GateDetailV1] = [] + self.lastSongInfo: LastSongDetail = LastSongDetail() + self.gateTutorialFlags: List[GateTutorialFlag] = [] + self.gatchaInfo: List[GachaInfo] = [] + self.friendList: List[FriendDetail] = [] + + def make(self) -> List: + super().make() + gates = [] + friends = [] + tut_flg = [] + + for x in self.gateInfo: + gates.append(x.make()) + + for x in self.friendList: + friends.append(x.make()) + + for x in self.gateTutorialFlags: + tut_flg.append(x.make()) + + while len(tut_flg) < 5: + flag_id = len(tut_flg) + 1 + tut_flg.append([flag_id, 0]) + + self.params.append(self.favorites) + self.params.append(self.stoppedSongIds) + self.params.append(self.eventInfo) + self.params.append(gates) + self.params.append(self.lastSongInfo.make()) + self.params.append(tut_flg) + self.params.append(self.gatchaInfo) + self.params.append(friends) + + return super(UserStatusGetDetailResponseV1, self).make() + +class UserStatusGetDetailResponseV3(UserStatusGetDetailResponseV2): + def __init__(self) -> None: + super().__init__() + self.gateInfo: List[GateDetailV2] = [] + +class UserStatusGetDetailResponseV4(UserStatusGetDetailResponseV3): + def __init__(self) -> None: + super().__init__() + self.userItems: UserItemInfoV3 = UserItemInfoV3() + self.bingoStatus: BingoDetail = BingoDetail(0) + self.scores: List[BestScoreDetailV2] = [] + + def make(self) -> List: + super().make() + self.params.append(self.bingoStatus.make()) + + return super(UserStatusGetDetailResponseV1, self).make() + +# ---user/status/login---- +class UserStatusLoginRequest(BaseRequest): + userId: int = 0 + + def __init__(self, data: Dict) -> None: + super().__init__(data) + self.userId = data["params"][0] + +class UserStatusLoginResponseV1(BaseResponse): + def __init__(self, is_first_login_daily: bool = False, last_login_date: int = 0) -> None: + super().__init__() + self.dailyBonus: List[LoginBonusInfo] = [] + self.consecBonus: List[LoginBonusInfo] = [] + self.otherBonus: List[LoginBonusInfo] = [] + self.firstLoginDaily = is_first_login_daily + self.lastLoginDate = last_login_date + + def make(self) -> List: + daily = [] + consec = [] + other = [] + + for bonus in self.dailyBonus: + daily.append(bonus.make()) + + for bonus in self.consecBonus: + consec.append(bonus.make()) + + for bonus in self.otherBonus: + other.append(bonus.make()) + + self.params = [ daily, consec, other, int(self.firstLoginDaily)] + return super().make() + +class UserStatusLoginResponseV2(UserStatusLoginResponseV1): + vipInfo: VipInfo + lastLoginDate: int = 0 + + def __init__(self, is_first_login_daily: bool = False, last_login_date: int = 0) -> None: + super().__init__(is_first_login_daily) + self.lastLoginDate = last_login_date + + self.vipInfo = VipInfo() + + def make(self) -> List: + super().make() + self.params.append(self.vipInfo.make()) + self.params.append(self.lastLoginDate) + return super(UserStatusLoginResponseV1, self).make() + +class UserStatusLoginResponseV3(UserStatusLoginResponseV2): + unk: List = [] + + def make(self) -> List: + super().make() + self.params.append(self.unk) + return super(UserStatusLoginResponseV1, self).make() + +# ---user/status/create--- +class UserStatusCreateRequest(BaseRequest): + def __init__(self, data: Dict) -> None: + super().__init__(data) + self.aimeId = data["params"][0] + self.username = data["params"][1] + +class UserStatusCreateResponseV1(BaseResponse): + def __init__(self, userId: int, username: str) -> None: + super().__init__() + self.userStatus = UserStatusV1() + self.userStatus.userId = userId + self.userStatus.username = username + + def make(self) -> List: + self.params = [ + self.userStatus.make() + ] + return super().make() + +class UserStatusCreateResponseV2(UserStatusCreateResponseV1): + def __init__(self, userId: int, username: str) -> None: + super().__init__(userId, username) + self.userStatus: UserStatusV2 = UserStatusV2() + self.userStatus.userId = userId + self.userStatus.username = username + +# ---user/status/logout--- +class UserStatusLogoutRequest(BaseRequest): + userId: int + + def __init__(self, data: Dict) -> None: + super().__init__(data) + self.userId = data["params"][0] + +# ---user/status/update--- +class UserStatusUpdateRequestV1(BaseRequest): + def __init__(self, data: Dict) -> None: + super().__init__(data) + self.profileId: int = data["params"][0] + self.playType: PlayType = PlayType(data["params"][1]) + self.itemsRecieved: List[GenericItemRecv] = [] + + for itm in data["params"][2]: + self.itemsRecieved.append(GenericItemRecv(itm[0], itm[1], itm[2])) + +class UserStatusUpdateRequestV2(UserStatusUpdateRequestV1): + isContinue = False + isFirstPlayFree = False + itemsUsed = [] + lastSongInfo: LastSongDetail + + def __init__(self, data: Dict) -> None: + super().__init__(data) + self.isContinue = bool(data["params"][3]) + self.isFirstPlayFree = bool(data["params"][4]) + self.itemsUsed = data["params"][5] + self.lastSongInfo = LastSongDetail(data["params"][6][0], data["params"][6][1], + data["params"][6][2], data["params"][6][3], data["params"][6][4]) diff --git a/titles/wacca/handlers/user_trial.py b/titles/wacca/handlers/user_trial.py new file mode 100644 index 0000000..84bd44a --- /dev/null +++ b/titles/wacca/handlers/user_trial.py @@ -0,0 +1,48 @@ +from typing import Dict, List +from titles.wacca.handlers.base import BaseRequest, BaseResponse +from titles.wacca.handlers.helpers import StageInfo, StageupClearType + +# --user/trial/get-- +class UserTrialGetRequest(BaseRequest): + profileId: int = 0 + + def __init__(self, data: Dict) -> None: + super().__init__(data) + self.profileId = self.params[0] + +class UserTrialGetResponse(BaseResponse): + def __init__(self) -> None: + super().__init__() + + self.stageList: List[StageInfo] = [] + + def make(self) -> Dict: + dans = [] + for x in self.stageList: + dans.append(x.make()) + + self.params = [dans] + return super().make() + +# --user/trial/update-- +class UserTrialUpdateRequest(BaseRequest): + def __init__(self, data: Dict) -> None: + super().__init__(data) + self.profileId = self.params[0] + self.stageId = self.params[1] + self.stageLevel = self.params[2] + self.clearType = StageupClearType(self.params[3]) + self.songScores = self.params[4] + self.numSongsCleared = self.params[5] + self.itemsObtained = self.params[6] + self.unk7: List = [] + + if len(self.params) == 8: + self.unk7 = self.params[7] + +class UserTrialUpdateResponse(BaseResponse): + def __init__(self) -> None: + super().__init__() + + def make(self) -> Dict: + return super().make() \ No newline at end of file diff --git a/titles/wacca/handlers/user_vip.py b/titles/wacca/handlers/user_vip.py new file mode 100644 index 0000000..c48c9fa --- /dev/null +++ b/titles/wacca/handlers/user_vip.py @@ -0,0 +1,54 @@ +from typing import Dict, List +from titles.wacca.handlers.base import BaseRequest, BaseResponse +from titles.wacca.handlers.helpers import VipLoginBonus + +# --user/vip/get-- +class UserVipGetRequest(BaseRequest): + def __init__(self, data: Dict) -> None: + super().__init__(data) + self.profileId = self.params[0] + +class UserVipGetResponse(BaseResponse): + def __init__(self) -> None: + super().__init__() + self.vipDays: int = 0 + self.unknown1: int = 1 + self.unknown2: int = 1 + self.presents: List[VipLoginBonus] = [] + + def make(self) -> Dict: + pres = [] + for x in self.presents: + pres.append(x.make()) + + self.params = [ + self.vipDays, + [ + self.unknown1, + self.unknown2, + pres + ] + ] + return super().make() + +# --user/vip/start-- +class UserVipStartRequest(BaseRequest): + def __init__(self, data: Dict) -> None: + super().__init__(data) + self.profileId = self.params[0] + self.cost = self.params[1] + self.days = self.params[2] + +class UserVipStartResponse(BaseResponse): + def __init__(self, expires: int = 0) -> None: + super().__init__() + self.whenExpires: int = expires + self.presents = [] + + def make(self) -> Dict: + self.params = [ + self.whenExpires, + self.presents + ] + + return super().make() \ No newline at end of file diff --git a/titles/wacca/index.py b/titles/wacca/index.py new file mode 100644 index 0000000..963fa7a --- /dev/null +++ b/titles/wacca/index.py @@ -0,0 +1,126 @@ +import yaml +import logging, coloredlogs +from logging.handlers import TimedRotatingFileHandler +import logging +import json +from datetime import datetime +from hashlib import md5 +from twisted.web.http import Request +from typing import Dict + +from core.config import CoreConfig +from titles.wacca.config import WaccaConfig +from titles.wacca.config import WaccaConfig +from titles.wacca.const import WaccaConstants +from titles.wacca.reverse import WaccaReverse +from titles.wacca.lilyr import WaccaLilyR +from titles.wacca.lily import WaccaLily +from titles.wacca.s import WaccaS +from titles.wacca.base import WaccaBase +from titles.wacca.handlers.base import BaseResponse + +class WaccaServlet(): + def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None: + self.core_cfg = core_cfg + self.game_cfg = WaccaConfig() + self.game_cfg.update(yaml.safe_load(open(f"{cfg_dir}/wacca.yaml"))) + + self.versions = [ + WaccaBase(core_cfg, self.game_cfg), + WaccaS(core_cfg, self.game_cfg), + WaccaLily(core_cfg, self.game_cfg), + WaccaLilyR(core_cfg, self.game_cfg), + WaccaReverse(core_cfg, self.game_cfg), + ] + + self.logger = logging.getLogger("wacca") + log_fmt_str = "[%(asctime)s] Wacca | %(levelname)s | %(message)s" + log_fmt = logging.Formatter(log_fmt_str) + fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.core_cfg.server.log_dir, "wacca"), encoding='utf8', + when="d", backupCount=10) + + fileHandler.setFormatter(log_fmt) + + consoleHandler = logging.StreamHandler() + consoleHandler.setFormatter(log_fmt) + + self.logger.addHandler(fileHandler) + self.logger.addHandler(consoleHandler) + + self.logger.setLevel(self.game_cfg.server.loglevel) + coloredlogs.install(level=self.game_cfg.server.loglevel, logger=self.logger, fmt=log_fmt_str) + + def render_POST(self, request: Request, version: int, url_path: str) -> bytes: + def end(resp: Dict) -> bytes: + hash = md5(json.dumps(resp, ensure_ascii=False).encode()).digest() + request.responseHeaders.addRawHeader(b"X-Wacca-Hash", hash.hex().encode()) + return json.dumps(resp).encode() + + version_full = [] + + try: + req_json = json.loads(request.content.getvalue()) + version_full = req_json["appVersion"].split(".") + except: + self.logger.error(f"Failed to parse request toi {request.uri} -> {request.content.getvalue()}") + resp = BaseResponse() + resp.status = 1 + resp.message = "ä¸æ­£ãªãƒªã‚¯ã‚¨ã‚¹ãƒˆ エラーã§ã™" + return end(resp.make()) + + url_split = url_path.split("/") + start_req_idx = url_split.index("api") + 1 + + func_to_find = "handle_" + for x in range(len(url_split) - start_req_idx): + func_to_find += f"{url_split[x + start_req_idx]}_" + func_to_find += "request" + + ver_search = (int(version_full[0]) * 10000) + (int(version_full[1]) * 100) + int(version_full[2]) + + if ver_search < 15000: + internal_ver = WaccaConstants.VER_WACCA + + elif ver_search >= 15000 and ver_search < 20000: + internal_ver = WaccaConstants.VER_WACCA_S + + elif ver_search >= 20000 and ver_search < 25000: + internal_ver = WaccaConstants.VER_WACCA_LILY + + elif ver_search >= 25000 and ver_search < 30000: + internal_ver = WaccaConstants.VER_WACCA_LILY_R + + elif ver_search >= 30000: + internal_ver = WaccaConstants.VER_WACCA_REVERSE + + else: + self.logger.warning(f"Unsupported version ({req_json['appVersion']}) request {url_path} - {req_json}") + resp = BaseResponse() + resp.status = 1 + resp.message = "ä¸æ­£ãªã‚¢ãƒ—リãƒãƒ¼ã‚¸ãƒ§ãƒ³ã‚¨ãƒ©ãƒ¼ã§ã™" + return end(resp.make()) + + self.logger.info(f"v{req_json['appVersion']} {url_path} request from {request.getClientAddress().host} with chipId {req_json['chipId']}") + self.logger.debug(req_json) + + try: + handler = getattr(self.versions[internal_ver], func_to_find) + if handler is not None: + resp = handler(req_json) + + else: + self.logger.warn(f"{req_json['appVersion']} has no handler for {func_to_find}") + resp = None + + if resp is None: + resp = BaseResponse().make() + + self.logger.debug(f"{req_json['appVersion']} response {resp}") + return end(resp) + + except Exception as e: + self.logger.error(f"{req_json['appVersion']} Error handling method {url_path} -> {e}") + if self.game_cfg.server.loglevel <= logging.DEBUG: + raise + resp = BaseResponse().make() + return end(resp) diff --git a/titles/wacca/lily.py b/titles/wacca/lily.py new file mode 100644 index 0000000..7f9580f --- /dev/null +++ b/titles/wacca/lily.py @@ -0,0 +1,351 @@ +from typing import Any, List, Dict +from datetime import datetime, timedelta +import json + +from core.config import CoreConfig +from titles.wacca.s import WaccaS +from titles.wacca.config import WaccaConfig +from titles.wacca.const import WaccaConstants + +from titles.wacca.handlers import * + +class WaccaLily(WaccaS): + def __init__(self, cfg: CoreConfig, game_cfg: WaccaConfig) -> None: + super().__init__(cfg, game_cfg) + self.version = WaccaConstants.VER_WACCA_LILY + self.season = 2 + + self.OPTIONS_DEFAULTS["set_nav_id"] = 210002 + self.allowed_stages = [ + (2001, 1), + (2002, 2), + (2003, 3), + (2004, 4), + (2005, 5), + (2006, 6), + (2007, 7), + (2008, 8), + (2009, 9), + (2010, 10), + (2011, 11), + (2012, 12), + (2013, 13), + (2014, 14), + (210001, 0), + (210002, 0), + (210003, 0), + ] + + def handle_user_status_get_request(self, data: Dict) -> List[Any]: + req = UserStatusGetRequest(data) + resp = UserStatusGetV2Response() + ver_split = req.appVersion.split(".") + + profile = self.data.profile.get_profile(aime_id=req.aimeId) + if profile is None: + self.logger.info(f"No user exists for aime id {req.aimeId}") + resp.profileStatus = ProfileStatus.ProfileRegister + return resp.make() + + self.logger.info(f"User preview for {req.aimeId} from {req.chipId}") + if profile["last_game_ver"] is None: + profile_ver_split = ver_split + resp.lastGameVersion = req.appVersion + else: + profile_ver_split = profile["last_game_ver"].split(".") + resp.lastGameVersion = profile["last_game_ver"] + + resp.userStatus.userId = profile["id"] + resp.userStatus.username = profile["username"] + resp.userStatus.xp = profile["xp"] + resp.userStatus.danLevel = profile["dan_level"] + resp.userStatus.danType = profile["dan_type"] + resp.userStatus.wp = profile["wp"] + resp.userStatus.useCount = profile["login_count"] + resp.userStatus.loginDays = profile["login_count_days"] + resp.userStatus.loginConsecutiveDays = profile["login_count_days_consec"] + resp.userStatus.loginsToday = profile["login_count_today"] + resp.userStatus.rating = profile["rating"] + + set_title_id = self.data.profile.get_options(WaccaConstants.OPTIONS["set_title_id"], profile["user"]) + if set_title_id is None: + set_title_id = self.OPTIONS_DEFAULTS["set_title_id"] + resp.setTitleId = set_title_id + + set_icon_id = self.data.profile.get_options(WaccaConstants.OPTIONS["set_title_id"], profile["user"]) + if set_icon_id is None: + set_icon_id = self.OPTIONS_DEFAULTS["set_icon_id"] + resp.setIconId = set_icon_id + + if profile["last_login_date"].timestamp() < int(datetime.now().replace(hour=0,minute=0,second=0,microsecond=0).timestamp()): + resp.userStatus.loginsToday = 0 + + if profile["last_login_date"].timestamp() < int((datetime.now().replace(hour=0,minute=0,second=0,microsecond=0) - timedelta(days=1)).timestamp()): + resp.userStatus.loginConsecutiveDays = 0 + + if int(ver_split[0]) > int(profile_ver_split[0]): + resp.versionStatus = PlayVersionStatus.VersionUpgrade + + elif int(ver_split[0]) < int(profile_ver_split[0]): + resp.versionStatus = PlayVersionStatus.VersionTooNew + + else: + if int(ver_split[1]) > int(profile_ver_split[1]): + resp.versionStatus = PlayVersionStatus.VersionUpgrade + + elif int(ver_split[1]) < int(profile_ver_split[1]): + resp.versionStatus = PlayVersionStatus.VersionTooNew + + else: + if int(ver_split[2]) > int(profile_ver_split[2]): + resp.versionStatus = PlayVersionStatus.VersionUpgrade + + + elif int(ver_split[2]) < int(profile_ver_split[2]): + resp.versionStatus = PlayVersionStatus.VersionTooNew + + if profile["vip_expire_time"] is not None: + resp.userStatus.vipExpireTime = int(profile["vip_expire_time"].timestamp()) + + if profile["always_vip"] or self.game_config.mods.always_vip: + resp.userStatus.vipExpireTime = int((datetime.now() + timedelta(days=30)).timestamp()) + + if self.game_config.mods.infinite_wp: + resp.userStatus.wp = 999999 + + return resp.make() + + def handle_user_status_login_request(self, data: Dict) -> List[Any]: + req = UserStatusLoginRequest(data) + resp = UserStatusLoginResponseV2() + is_new_day = False + is_consec_day = False + is_consec_day = True + + if req.userId == 0: + self.logger.info(f"Guest login on {req.chipId}") + resp.lastLoginDate = 0 + + else: + profile = self.data.profile.get_profile(req.userId) + if profile is None: + self.logger.warn(f"Unknown user id {req.userId} attempted login from {req.chipId}") + return resp.make() + + self.logger.info(f"User {req.userId} login on {req.chipId}") + last_login_time = int(profile["last_login_date"].timestamp()) + resp.lastLoginDate = last_login_time + + # If somebodies login timestamp < midnight of current day, then they are logging in for the first time today + if last_login_time < int(datetime.now().replace(hour=0,minute=0,second=0,microsecond=0).timestamp()): + is_new_day = True + is_consec_day = True + + # If somebodies login timestamp > midnight of current day + 1 day, then they broke their daily login streak + elif last_login_time > int((datetime.now().replace(hour=0,minute=0,second=0,microsecond=0) + timedelta(days=1)).timestamp()): + is_consec_day = False + # else, they are simply logging in again on the same day, and we don't need to do anything for that + + self.data.profile.session_login(req.userId, is_new_day, is_consec_day) + resp.vipInfo.pageYear = datetime.now().year + resp.vipInfo.pageMonth = datetime.now().month + resp.vipInfo.pageDay = datetime.now().day + resp.vipInfo.numItem = 1 + + resp.firstLoginDaily = int(is_new_day) + + return resp.make() + + def handle_user_status_getDetail_request(self, data: Dict) -> List[Any]: + req = UserStatusGetDetailRequest(data) + ver_split = req.appVersion.split(".") + if int(ver_split[1]) >= 53: + resp = UserStatusGetDetailResponseV3() + else: + resp = UserStatusGetDetailResponseV2() + + profile = self.data.profile.get_profile(req.userId) + if profile is None: + self.logger.warn(f"Unknown profile {req.userId}") + return resp.make() + + self.logger.info(f"Get detail for profile {req.userId}") + user_id = profile["user"] + + profile_scores = self.data.score.get_best_scores(user_id) + profile_items = self.data.item.get_items(user_id) + profile_song_unlocks = self.data.item.get_song_unlocks(user_id) + profile_options = self.data.profile.get_options(user_id) + profile_favorites = self.data.profile.get_favorite_songs(user_id) + profile_gates = self.data.profile.get_gates(user_id) + profile_trophies = self.data.item.get_trophies(user_id) + profile_tickets = self.data.item.get_tickets(user_id) + + if profile["vip_expire_time"] is None: + resp.userStatus.vipExpireTime = 0 + + else: + resp.userStatus.vipExpireTime = int(profile["vip_expire_time"].timestamp()) + + if profile["always_vip"] or self.game_config.mods.always_vip: + resp.userStatus.vipExpireTime = int((self.srvtime + timedelta(days=31)).timestamp()) + + resp.songUpdateTime = int(profile["last_login_date"].timestamp()) + resp.lastSongInfo = LastSongDetail(profile["last_song_id"],profile["last_song_difficulty"],profile["last_folder_order"],profile["last_folder_id"],profile["last_song_order"]) + resp.songPlayStatus = [resp.lastSongInfo.lastSongId, 1] + + resp.userStatus.userId = profile["id"] + resp.userStatus.username = profile["username"] + resp.userStatus.xp = profile["xp"] + resp.userStatus.danLevel = profile["dan_level"] + resp.userStatus.danType = profile["dan_type"] + resp.userStatus.wp = profile["wp"] + resp.userStatus.useCount = profile["login_count"] + resp.userStatus.loginDays = profile["login_count_days"] + resp.userStatus.loginConsecutiveDays = profile["login_count_days_consec"] + resp.userStatus.loginsToday = profile["login_count_today"] + resp.userStatus.rating = profile['rating'] + + if self.game_config.mods.infinite_wp: + resp.userStatus.wp = 999999 + + for fav in profile_favorites: + resp.favorites.append(fav["song_id"]) + + if profile["friend_view_1"] is not None: + pass + if profile["friend_view_2"] is not None: + pass + if profile["friend_view_3"] is not None: + pass + + resp.seasonalPlayModeCounts.append(PlayModeCounts(self.season, 1, profile["playcount_single"])) + resp.seasonalPlayModeCounts.append(PlayModeCounts(self.season, 2, profile["playcount_multi_vs"])) + resp.seasonalPlayModeCounts.append(PlayModeCounts(self.season, 3, profile["playcount_multi_coop"])) + resp.seasonalPlayModeCounts.append(PlayModeCounts(self.season, 4, profile["playcount_stageup"])) + + for opt in profile_options: + resp.options.append(UserOption(opt["opt_id"], opt["value"])) + + for gate in self.game_config.gates.enabled_gates: + added_gate = False + + for user_gate in profile_gates: + if user_gate["gate_id"] == gate: + if int(ver_split[1]) >= 53: + resp.gateInfo.append(GateDetailV2(user_gate["gate_id"],user_gate["page"],user_gate["progress"], + user_gate["loops"],int(user_gate["last_used"].timestamp()),user_gate["mission_flag"])) + + else: + resp.gateInfo.append(GateDetailV1(user_gate["gate_id"],user_gate["page"],user_gate["progress"], + user_gate["loops"],int(user_gate["last_used"].timestamp()),user_gate["mission_flag"])) + + resp.seasonInfo.cumulativeGatePts += user_gate["total_points"] + + added_gate = True + break + + if not added_gate: + if int(ver_split[1]) >= 53: + resp.gateInfo.append(GateDetailV2(gate)) + + else: + resp.gateInfo.append(GateDetailV1(gate)) + + for unlock in profile_song_unlocks: + for x in range(1, unlock["highest_difficulty"] + 1): + resp.userItems.songUnlocks.append(SongUnlock(unlock["song_id"], x, 0, int(unlock["acquire_date"].timestamp()))) + if x > 2: + resp.scores.append(BestScoreDetailV1(unlock["song_id"], x)) + + empty_scores = len(resp.scores) + for song in profile_scores: + resp.seasonInfo.cumulativeScore += song["score"] + empty_score_idx = resp.find_score_idx(song["song_id"], song["chart_id"], 0, empty_scores) + + clear_cts = SongDetailClearCounts( + song["play_ct"], + song["clear_ct"], + song["missless_ct"], + song["fullcombo_ct"], + song["allmarv_ct"], + ) + + grade_cts = SongDetailGradeCountsV1( + song["grade_d_ct"], song["grade_c_ct"], song["grade_b_ct"], song["grade_a_ct"], song["grade_aa_ct"], + song["grade_aaa_ct"], song["grade_s_ct"], song["grade_ss_ct"], song["grade_sss_ct"], + song["grade_master_ct"] + ) + + if empty_score_idx is not None: + resp.scores[empty_score_idx].clearCounts = clear_cts + resp.scores[empty_score_idx].clearCountsSeason = clear_cts + resp.scores[empty_score_idx].gradeCounts = grade_cts + resp.scores[empty_score_idx].score = song["score"] + resp.scores[empty_score_idx].bestCombo = song["best_combo"] + resp.scores[empty_score_idx].lowestMissCtMaybe = song["lowest_miss_ct"] + resp.scores[empty_score_idx].rating = song["rating"] + + else: + deets = BestScoreDetailV1(song["song_id"], song["chart_id"]) + deets.clearCounts = clear_cts + deets.clearCountsSeason = clear_cts + deets.gradeCounts = grade_cts + deets.score = song["score"] + deets.bestCombo = song["best_combo"] + deets.lowestMissCtMaybe = song["lowest_miss_ct"] + deets.rating = song["rating"] + + for trophy in profile_trophies: + resp.userItems.trophies.append(TrophyItem(trophy["trophy_id"], trophy["season"], trophy["progress"], trophy["badge_type"])) + + if self.game_config.mods.infinite_tickets: + for x in range(5): + resp.userItems.tickets.append(TicketItem(x, 106002, 0)) + else: + for ticket in profile_tickets: + if ticket["expire_date"] is None: + expire = int((self.srvtime + timedelta(days=30)).timestamp()) + else: + expire = int(ticket["expire_date"].timestamp()) + + resp.userItems.tickets.append(TicketItem(ticket["id"], ticket["ticket_id"], expire)) + + if profile_items: + for item in profile_items: + try: + + if item["type"] == WaccaConstants.ITEM_TYPES["icon"]: + resp.userItems.icons.append(IconItem(item["item_id"], 1, item["use_count"], int(item["acquire_date"].timestamp()))) + + elif item["type"] == WaccaConstants.ITEM_TYPES["navigator"]: + resp.userItems.navigators.append(NavigatorItem(item["item_id"], 1, int(item["acquire_date"].timestamp()), item["use_count"], item["use_count"])) + + else: + itm_send = GenericItemSend(item["item_id"], 1, int(item["acquire_date"].timestamp())) + + if item["type"] == WaccaConstants.ITEM_TYPES["title"]: + resp.userItems.titles.append(itm_send) + + elif item["type"] == WaccaConstants.ITEM_TYPES["user_plate"]: + resp.userItems.plates.append(itm_send) + + elif item["type"] == WaccaConstants.ITEM_TYPES["note_color"]: + resp.userItems.noteColors.append(itm_send) + + elif item["type"] == WaccaConstants.ITEM_TYPES["note_sound"]: + resp.userItems.noteSounds.append(itm_send) + + except: + self.logger.error(f"{__name__} Failed to load item {item['item_id']} for user {profile['user']}") + + resp.seasonInfo.level = profile["xp"] + resp.seasonInfo.wpObtained = profile["wp_total"] + resp.seasonInfo.wpSpent = profile["wp_spent"] + resp.seasonInfo.titlesObtained = len(resp.userItems.titles) + resp.seasonInfo.iconsObtained = len(resp.userItems.icons) + resp.seasonInfo.noteColorsObtained = len(resp.userItems.noteColors) + resp.seasonInfo.noteSoundsObtained = len(resp.userItems.noteSounds) + resp.seasonInfo.platesObtained = len(resp.userItems.plates) + + return resp.make() \ No newline at end of file diff --git a/titles/wacca/lilyr.py b/titles/wacca/lilyr.py new file mode 100644 index 0000000..a359725 --- /dev/null +++ b/titles/wacca/lilyr.py @@ -0,0 +1,54 @@ +from typing import Any, List, Dict +from datetime import datetime, timedelta +import json + +from core.config import CoreConfig +from titles.wacca.lily import WaccaLily +from titles.wacca.config import WaccaConfig +from titles.wacca.const import WaccaConstants +from titles.wacca.handlers import * + +class WaccaLilyR(WaccaLily): + def __init__(self, cfg: CoreConfig, game_cfg: WaccaConfig) -> None: + super().__init__(cfg, game_cfg) + self.version = WaccaConstants.VER_WACCA_LILY_R + self.season = 2 + + self.OPTIONS_DEFAULTS["set_nav_id"] = 210002 + self.allowed_stages = [ + (2501, 1), + (2502, 2), + (2503, 3), + (2504, 4), + (2505, 5), + (2506, 6), + (2507, 7), + (2508, 8), + (2509, 9), + (2510, 10), + (2511, 11), + (2512, 12), + (2513, 13), + (2514, 14), + (210001, 0), + (210002, 0), + (210003, 0), + ] + + def handle_user_status_create_request(self, data: Dict) -> List[Any]: + req = UserStatusCreateRequest(data) + resp = super().handle_user_status_create_request(data) + + self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210054) # Added lily r + self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210055) # Added lily r + self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210056) # Added lily r + self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210057) # Added lily r + self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210058) # Added lily r + self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210059) # Added lily r + self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210060) # Added lily r + self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210061) # Added lily r + + return resp + + def handle_user_status_logout_request(self, data: Dict) -> List[Any]: + return BaseResponse().make() diff --git a/titles/wacca/read.py b/titles/wacca/read.py new file mode 100644 index 0000000..1c8e7f8 --- /dev/null +++ b/titles/wacca/read.py @@ -0,0 +1,80 @@ +from typing import Optional +import wacky +import json +from os import walk, path + +from read import BaseReader +from core.config import CoreConfig +from titles.wacca.database import WaccaData +from titles.wacca.const import WaccaConstants + +class WaccaReader(BaseReader): + def __init__(self, config: CoreConfig, version: int, bin_dir: Optional[str], opt_dir: Optional[str], extra: Optional[str]) -> None: + super().__init__(config, version, bin_dir, opt_dir, extra) + self.data = WaccaData(config) + + try: + self.logger.info(f"Start importer for {WaccaConstants.game_ver_to_string(version)}") + except IndexError: + self.logger.error(f"Invalid wacca version {version}") + exit(1) + + def read(self) -> None: + if not (path.exists(f"{self.bin_dir}/Table") and path.exists(f"{self.bin_dir}/Message")): + self.logger.error("Could not find Table or Message folder, nothing to read") + return + + self.read_music(f"{self.bin_dir}/Table", "MusicParameterTable") + + def read_music(self, base_dir: str, table: str) -> None: + if not self.check_valid_pair(base_dir, table): + self.logger.warn(f"Cannot find {table} uasset/uexp pair at {base_dir}, music will not be read") + return + + uasset=open(f"{base_dir}/{table}.uasset", "rb") + uexp=open(f"{base_dir}/{table}.uexp", "rb") + + package = wacky.jsonify(uasset,uexp) + package_json = json.dumps(package, indent=4, sort_keys=True) + data=json.loads(package_json) + + first_elem = data[0] + wacca_data = first_elem['rows'] + + for i, key in enumerate(wacca_data): + song_id = int(key) + title = wacca_data[str(key)]["MusicMessage"] + artist = wacca_data[str(key)]["ArtistMessage"] + bpm = wacca_data[str(key)]["Bpm"] + jacket_asset_name = wacca_data[str(key)]["JacketAssetName"] + + diff = float(wacca_data[str(key)]["DifficultyNormalLv"]) + designer = wacca_data[str(key)]["NotesDesignerNormal"] + + if diff > 0: + self.data.static.put_music(self.version, song_id, 1, title, artist, bpm, diff, designer, jacket_asset_name) + self.logger.info(f"Read song {song_id} chart 1") + + diff = float(wacca_data[str(key)]["DifficultyHardLv"]) + designer = wacca_data[str(key)]["NotesDesignerHard"] + + if diff > 0: + self.data.static.put_music(self.version, song_id, 2, title, artist, bpm, diff, designer, jacket_asset_name) + self.logger.info(f"Read song {song_id} chart 2") + + diff = float(wacca_data[str(key)]["DifficultyExtremeLv"]) + designer = wacca_data[str(key)]["NotesDesignerExpert"] + + if diff > 0: + self.data.static.put_music(self.version, song_id, 3, title, artist, bpm, diff, designer, jacket_asset_name) + self.logger.info(f"Read song {song_id} chart 3") + + diff = float(wacca_data[str(key)]["DifficultyInfernoLv"]) + designer = wacca_data[str(key)]["NotesDesignerInferno"] + + if diff > 0: + self.data.static.put_music(self.version, song_id, 4, title, artist, bpm, diff, designer, jacket_asset_name) + self.logger.info(f"Read song {song_id} chart 4") + + def check_valid_pair(self, dir: str, file: str) -> bool: + return path.exists(f"{dir}/{file}.uasset") and path.exists(f"{dir}/{file}.uexp") \ No newline at end of file diff --git a/titles/wacca/reverse.py b/titles/wacca/reverse.py new file mode 100644 index 0000000..3d2b571 --- /dev/null +++ b/titles/wacca/reverse.py @@ -0,0 +1,258 @@ +from typing import Any, List, Dict +from datetime import datetime, timedelta +import json + +from core.config import CoreConfig +from titles.wacca.lilyr import WaccaLilyR +from titles.wacca.config import WaccaConfig +from titles.wacca.const import WaccaConstants + +from titles.wacca.handlers import * + +class WaccaReverse(WaccaLilyR): + def __init__(self, cfg: CoreConfig, game_cfg: WaccaConfig) -> None: + super().__init__(cfg, game_cfg) + self.version = WaccaConstants.VER_WACCA_REVERSE + + self.season = 3 + + self.OPTIONS_DEFAULTS["set_nav_id"] = 310001 + self.allowed_stages = [ + (3001, 1), + (3002, 2), + (3003, 3), + (3004, 4), + (3005, 5), + (3006, 6), + (3007, 7), + (3008, 8), + (3009, 9), + (3010, 10), + (3011, 11), + (3012, 12), + (3013, 13), + (3014, 14), + # Touhou + (210001, 0), + (210002, 0), + (210003, 0), + # Final spurt + (310001, 0), + (310002, 0), + (310003, 0), + # boss remix + (310004, 0), + (310005, 0), + (310006, 0), + ] + + def handle_user_status_login_request(self, data: Dict) -> List[Any]: + resp = super().handle_user_status_login_request(data) + resp["params"].append([]) + return resp + + def handle_user_status_getDetail_request(self, data: Dict) -> List[Any]: + req = UserStatusGetDetailRequest(data) + resp = UserStatusGetDetailResponseV4() + + profile = self.data.profile.get_profile(req.userId) + if profile is None: + self.logger.warn(f"Unknown profile {req.userId}") + return resp.make() + + self.logger.info(f"Get detail for profile {req.userId}") + user_id = profile["user"] + + profile_scores = self.data.score.get_best_scores(user_id) + profile_items = self.data.item.get_items(user_id) + profile_song_unlocks = self.data.item.get_song_unlocks(user_id) + profile_options = self.data.profile.get_options(user_id) + profile_favorites = self.data.profile.get_favorite_songs(user_id) + profile_gates = self.data.profile.get_gates(user_id) + profile_bingo = self.data.profile.get_bingo(user_id) + profile_trophies = self.data.item.get_trophies(user_id) + profile_tickets = self.data.item.get_tickets(user_id) + + if profile["gate_tutorial_flags"] is not None: + for x in profile["gate_tutorial_flags"]: + resp.gateTutorialFlags.append(GateTutorialFlag(x[0], x[1])) + + if profile["vip_expire_time"] is None: + resp.userStatus.vipExpireTime = 0 + + else: + resp.userStatus.vipExpireTime = int(profile["vip_expire_time"].timestamp()) + + if profile["always_vip"] or self.game_config.mods.always_vip: + resp.userStatus.vipExpireTime = int((self.srvtime + timedelta(days=31)).timestamp()) + + resp.songUpdateTime = int(profile["last_login_date"].timestamp()) + resp.lastSongInfo = LastSongDetail(profile["last_song_id"],profile["last_song_difficulty"],profile["last_folder_order"],profile["last_folder_id"],profile["last_song_order"]) + resp.songPlayStatus = [resp.lastSongInfo.lastSongId, 1] + + resp.userStatus.userId = profile["id"] + resp.userStatus.username = profile["username"] + resp.userStatus.xp = profile["xp"] + resp.userStatus.danLevel = profile["dan_level"] + resp.userStatus.danType = profile["dan_type"] + resp.userStatus.wp = profile["wp"] + resp.userStatus.useCount = profile["login_count"] + resp.userStatus.loginDays = profile["login_count_days"] + resp.userStatus.loginConsecutiveDays = profile["login_count_days_consec"] + resp.userStatus.loginsToday = profile["login_count_today"] + resp.userStatus.rating = profile['rating'] + + if self.game_config.mods.infinite_wp: + resp.userStatus.wp = 999999 + + for fav in profile_favorites: + resp.favorites.append(fav["song_id"]) + + if profile["friend_view_1"] is not None: + pass + if profile["friend_view_2"] is not None: + pass + if profile["friend_view_3"] is not None: + pass + + resp.seasonalPlayModeCounts.append(PlayModeCounts(self.season, 1, profile["playcount_single"])) + resp.seasonalPlayModeCounts.append(PlayModeCounts(self.season, 2, profile["playcount_multi_vs"])) + resp.seasonalPlayModeCounts.append(PlayModeCounts(self.season, 3, profile["playcount_multi_coop"])) + resp.seasonalPlayModeCounts.append(PlayModeCounts(self.season, 4, profile["playcount_stageup"])) + + for opt in profile_options: + resp.options.append(UserOption(opt["opt_id"], opt["value"])) + + if profile_bingo is not None: + resp.bingoStatus = BingoDetail(profile_bingo["page_number"]) + for x in profile_bingo["page_progress"]: + resp.bingoStatus.pageStatus.append(BingoPageStatus(x[0], x[1], x[2])) + + for gate in self.game_config.gates.enabled_gates: + added_gate = False + + for user_gate in profile_gates: + if user_gate["gate_id"] == gate: + + resp.gateInfo.append(GateDetailV2(user_gate["gate_id"],user_gate["page"],user_gate["progress"], + user_gate["loops"],int(user_gate["last_used"].timestamp()),user_gate["mission_flag"])) + + resp.seasonInfo.cumulativeGatePts += user_gate["total_points"] + + added_gate = True + break + + if not added_gate: + resp.gateInfo.append(GateDetailV2(gate)) + + for unlock in profile_song_unlocks: + for x in range(1, unlock["highest_difficulty"] + 1): + resp.userItems.songUnlocks.append(SongUnlock(unlock["song_id"], x, 0, int(unlock["acquire_date"].timestamp()))) + if x > 2: + resp.scores.append(BestScoreDetailV2(unlock["song_id"], x)) + + empty_scores = len(resp.scores) + for song in profile_scores: + resp.seasonInfo.cumulativeScore += song["score"] + empty_score_idx = resp.find_score_idx(song["song_id"], song["chart_id"], 0, empty_scores) + + clear_cts = SongDetailClearCounts( + song["play_ct"], + song["clear_ct"], + song["missless_ct"], + song["fullcombo_ct"], + song["allmarv_ct"], + ) + + grade_cts = SongDetailGradeCountsV2( + song["grade_d_ct"], song["grade_c_ct"], song["grade_b_ct"], song["grade_a_ct"], song["grade_aa_ct"], + song["grade_aaa_ct"], song["grade_s_ct"], song["grade_ss_ct"], song["grade_sss_ct"], + song["grade_master_ct"], song["grade_sp_ct"], song["grade_ssp_ct"], song["grade_sssp_ct"] + ) + + if empty_score_idx is not None: + resp.scores[empty_score_idx].clearCounts = clear_cts + resp.scores[empty_score_idx].clearCountsSeason = clear_cts + resp.scores[empty_score_idx].gradeCounts = grade_cts + resp.scores[empty_score_idx].score = song["score"] + resp.scores[empty_score_idx].bestCombo = song["best_combo"] + resp.scores[empty_score_idx].lowestMissCtMaybe = song["lowest_miss_ct"] + resp.scores[empty_score_idx].rating = song["rating"] + + else: + deets = BestScoreDetailV2(song["song_id"], song["chart_id"]) + deets.clearCounts = clear_cts + deets.clearCountsSeason = clear_cts + deets.gradeCounts = grade_cts + deets.score = song["score"] + deets.bestCombo = song["best_combo"] + deets.lowestMissCtMaybe = song["lowest_miss_ct"] + deets.rating = song["rating"] + resp.scores.append(deets) + + for trophy in profile_trophies: + resp.userItems.trophies.append(TrophyItem(trophy["trophy_id"], trophy["season"], trophy["progress"], trophy["badge_type"])) + + if self.game_config.mods.infinite_tickets: + for x in range(5): + resp.userItems.tickets.append(TicketItem(x, 106002, 0)) + else: + for ticket in profile_tickets: + if ticket["expire_date"] is None: + expire = int((self.srvtime + timedelta(days=30)).timestamp()) + else: + expire = int(ticket["expire_date"].timestamp()) + + resp.userItems.tickets.append(TicketItem(ticket["id"], ticket["ticket_id"], expire)) + + if profile_items: + for item in profile_items: + try: + + if item["type"] == WaccaConstants.ITEM_TYPES["icon"]: + resp.userItems.icons.append(IconItem(item["item_id"], 1, item["use_count"], int(item["acquire_date"].timestamp()))) + + elif item["type"] == WaccaConstants.ITEM_TYPES["navigator"]: + resp.userItems.navigators.append(NavigatorItem(item["item_id"], 1, int(item["acquire_date"].timestamp()), item["use_count"], item["use_count"])) + + else: + itm_send = GenericItemSend(item["item_id"], 1, int(item["acquire_date"].timestamp())) + + if item["type"] == WaccaConstants.ITEM_TYPES["title"]: + resp.userItems.titles.append(itm_send) + + elif item["type"] == WaccaConstants.ITEM_TYPES["user_plate"]: + resp.userItems.plates.append(itm_send) + + elif item["type"] == WaccaConstants.ITEM_TYPES["touch_effect"]: + resp.userItems.touchEffect.append(itm_send) + + elif item["type"] == WaccaConstants.ITEM_TYPES["note_color"]: + resp.userItems.noteColors.append(itm_send) + + elif item["type"] == WaccaConstants.ITEM_TYPES["note_sound"]: + resp.userItems.noteSounds.append(itm_send) + + except: + self.logger.error(f"{__name__} Failed to load item {item['item_id']} for user {profile['user']}") + + resp.seasonInfo.level = profile["xp"] + resp.seasonInfo.wpObtained = profile["wp_total"] + resp.seasonInfo.wpSpent = profile["wp_spent"] + resp.seasonInfo.titlesObtained = len(resp.userItems.titles) + resp.seasonInfo.iconsObtained = len(resp.userItems.icons) + resp.seasonInfo.noteColorsObtained = len(resp.userItems.noteColors) + resp.seasonInfo.noteSoundsObtained = len(resp.userItems.noteSounds) + resp.seasonInfo.platesObtained = len(resp.userItems.plates) + + return resp.make() + + def handle_user_status_create_request(self, data: Dict) -> List[Any]: + req = UserStatusCreateRequest(data) + resp = super().handle_user_status_create_request(data) + + self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 310001) # Added reverse + self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 310002) # Added reverse + + return resp + diff --git a/titles/wacca/s.py b/titles/wacca/s.py new file mode 100644 index 0000000..f302f44 --- /dev/null +++ b/titles/wacca/s.py @@ -0,0 +1,35 @@ +from typing import Any, List, Dict +from datetime import datetime, timedelta +import json + +from core.config import CoreConfig +from titles.wacca.base import WaccaBase +from titles.wacca.config import WaccaConfig +from titles.wacca.const import WaccaConstants + +from titles.wacca.handlers import * + +class WaccaS(WaccaBase): + allowed_stages = [ + (1501, 1), + (1502, 2), + (1503, 3), + (1504, 4), + (1505, 5), + (1506, 6), + (1507, 7), + (1508, 8), + (1509, 9), + (1510, 10), + (1511, 11), + (1512, 12), + (1513, 13), + ] + + def __init__(self, cfg: CoreConfig, game_cfg: WaccaConfig) -> None: + super().__init__(cfg, game_cfg) + self.version = WaccaConstants.VER_WACCA_S + + def handle_advertise_GetNews_request(self, data: Dict) -> List[Any]: + resp = GetNewsResponseV2() + return resp.make() diff --git a/titles/wacca/schema/__init__.py b/titles/wacca/schema/__init__.py new file mode 100644 index 0000000..1addd87 --- /dev/null +++ b/titles/wacca/schema/__init__.py @@ -0,0 +1,6 @@ +from titles.wacca.schema.profile import WaccaProfileData +from titles.wacca.schema.score import WaccaScoreData +from titles.wacca.schema.item import WaccaItemData +from titles.wacca.schema.static import WaccaStaticData + +__all__ = ["WaccaProfileData", "WaccaScoreData", "WaccaItemData", "WaccaStaticData"] \ No newline at end of file diff --git a/titles/wacca/schema/item.py b/titles/wacca/schema/item.py new file mode 100644 index 0000000..76e901f --- /dev/null +++ b/titles/wacca/schema/item.py @@ -0,0 +1,177 @@ +from typing import Optional, Dict, List +from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_, case +from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean +from sqlalchemy.schema import ForeignKey +from sqlalchemy.sql import func, select, update, delete +from sqlalchemy.engine import Row +from sqlalchemy.dialects.mysql import insert + +from core.data.schema import BaseData, metadata + +item = Table( + "wacca_item", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("item_id", Integer, nullable=False), + Column("type", Integer, nullable=False), + Column("acquire_date", TIMESTAMP, nullable=False, server_default=func.now()), + Column("use_count", Integer, server_default="0"), + UniqueConstraint("user", "item_id", "type", name="wacca_item_uk"), + mysql_charset='utf8mb4' +) + +ticket = Table( + "wacca_ticket", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("ticket_id", Integer, nullable=False), + Column("acquire_date", TIMESTAMP, nullable=False, server_default=func.now()), + Column("expire_date", TIMESTAMP), + mysql_charset='utf8mb4' +) + +song_unlock = Table( + "wacca_song_unlock", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("song_id", Integer, nullable=False), + Column("highest_difficulty", Integer, nullable=False), + Column("acquire_date", TIMESTAMP, nullable=False, server_default=func.now()), + UniqueConstraint("user", "song_id", name="wacca_song_unlock_uk"), + mysql_charset='utf8mb4' +) + +trophy = Table( + "wacca_trophy", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("trophy_id", Integer, nullable=False), + Column("season", Integer, nullable=False), + Column("progress", Integer, nullable=False, server_default="0"), + Column("badge_type", Integer, nullable=False, server_default="0"), + UniqueConstraint("user", "trophy_id", "season", name="wacca_trophy_uk"), + mysql_charset='utf8mb4' +) + +class WaccaItemData(BaseData): + def get_song_unlocks(self, user_id: int) -> Optional[List[Row]]: + sql = song_unlock.select(song_unlock.c.user == user_id) + + result = self.execute(sql) + if result is None: return None + + return result.fetchall() + + def unlock_song(self, user_id: int, song_id: int, difficulty: int) -> Optional[int]: + sql = insert(song_unlock).values( + user=user_id, + song_id=song_id, + highest_difficulty=difficulty + ) + + conflict = sql.on_duplicate_key_update( + highest_difficulty=case( + (song_unlock.c.highest_difficulty >= difficulty, song_unlock.c.highest_difficulty), + (song_unlock.c.highest_difficulty < difficulty, difficulty), + ) + ) + + result = self.execute(conflict) + if result is None: + self.logger.error(f"{__name__} failed to unlock song! user: {user_id}, song_id: {song_id}, difficulty: {difficulty}") + return None + + return result.lastrowid + + def put_item(self, user_id: int, item_type: int, item_id: int) -> Optional[int]: + sql = insert(item).values( + user = user_id, + item_id = item_id, + type = item_type, + ) + + conflict = sql.on_duplicate_key_update( + use_count = item.c.use_count + 1 + ) + + result = self.execute(conflict) + if result is None: + self.logger.error(f"{__name__} failed to insert item! user: {user_id}, item_id: {item_id}, item_type: {item_type}") + return None + + return result.lastrowid + + def get_items(self, user_id: int, item_type: int = None, item_id: int = None) -> Optional[List[Row]]: + """ + A catch-all item lookup given a profile and option item type and ID specifiers + """ + sql = item.select( + and_(item.c.user == user_id, + item.c.type == item_type if item_type is not None else True, + item.c.item_id == item_id if item_id is not None else True) + ) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def get_tickets(self, user_id: int) -> Optional[List[Row]]: + sql = select(ticket).where(ticket.c.user == user_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def add_ticket(self, user_id: int, ticket_id: int) -> None: + sql = insert(ticket).values( + user = user_id, + ticket_id = ticket_id + ) + + result = self.execute(sql) + if result is None: + self.logger.error(f"add_ticket: Failed to insert wacca ticket! user_id: {user_id} ticket_id {ticket_id}") + return None + return result.lastrowid + + def spend_ticket(self, id: int) -> None: + sql = delete(ticket).where(ticket.c.id == id) + + result = self.execute(sql) + if result is None: + self.logger.warn(f"Failed to delete ticket id {id}") + return None + + def get_trophies(self, user_id: int, season: int = None) -> Optional[List[Row]]: + if season is None: + sql = select(trophy).where(trophy.c.user == user_id) + else: + sql = select(trophy).where(and_(trophy.c.user == user_id, trophy.c.season == season)) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def update_trophy(self, user_id: int, trophy_id: int, season: int, progress: int, badge_type: int) -> Optional[int]: + sql = insert(trophy).values( + user = user_id, + trophy_id = trophy_id, + season = season, + progress = progress, + badge_type = badge_type + ) + + conflict = sql.on_duplicate_key_update( + progress = progress + ) + + result = self.execute(conflict) + if result is None: + self.logger.error(f"update_trophy: Failed to insert wacca trophy! user_id: {user_id} trophy_id: {trophy_id} progress {progress}") + return None + return result.lastrowid + diff --git a/titles/wacca/schema/profile.py b/titles/wacca/schema/profile.py new file mode 100644 index 0000000..7237149 --- /dev/null +++ b/titles/wacca/schema/profile.py @@ -0,0 +1,428 @@ +from typing import Optional, Dict, List +from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_ +from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON +from sqlalchemy.schema import ForeignKey +from sqlalchemy.sql import func, select +from sqlalchemy.engine import Row +from sqlalchemy.dialects.mysql import insert + +from core.data.schema import BaseData, metadata + +profile = Table( + "wacca_profile", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("version", Integer), + Column("username", String(8), nullable=False), + Column("xp", Integer, server_default="0"), + Column("wp", Integer, server_default="0"), + Column("wp_total", Integer, server_default="0"), + Column("wp_spent", Integer, server_default="0"), + Column("dan_type", Integer, server_default="0"), + Column("dan_level", Integer, server_default="0"), + Column("title_0", Integer, server_default="0"), + Column("title_1", Integer, server_default="0"), + Column("title_2", Integer, server_default="0"), + Column("rating", Integer, server_default="0"), + Column("vip_expire_time", TIMESTAMP), + Column("always_vip", Boolean, server_default="0"), + Column("login_count", Integer, server_default="0"), + Column("login_count_consec", Integer, server_default="0"), + Column("login_count_days", Integer, server_default="0"), + Column("login_count_days_consec", Integer, server_default="0"), + Column("login_count_today", Integer, server_default="0"), + Column("playcount_single", Integer, server_default="0"), + Column("playcount_multi_vs", Integer, server_default="0"), + Column("playcount_multi_coop", Integer, server_default="0"), + Column("playcount_stageup", Integer, server_default="0"), + Column("friend_view_1", Integer), + Column("friend_view_2", Integer), + Column("friend_view_3", Integer), + Column("last_game_ver", String(50)), + Column("last_song_id", Integer, server_default="0"), + Column("last_song_difficulty", Integer, server_default="0"), + Column("last_folder_order", Integer, server_default="0"), + Column("last_folder_id", Integer, server_default="0"), + Column("last_song_order", Integer, server_default="0"), + Column("last_login_date", TIMESTAMP, server_default=func.now()), + Column("gate_tutorial_flags", JSON), + UniqueConstraint("user", "version", name="wacca_profile_uk"), + mysql_charset='utf8mb4' +) + +option = Table( + "wacca_option", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("opt_id", Integer, nullable=False), + Column("value", Integer, nullable=False), + UniqueConstraint("user", "opt_id", name="wacca_option_uk"), +) + +bingo = Table( + "wacca_bingo", + metadata, + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), primary_key=True, nullable=False), + Column("page_number", Integer, nullable=False), + Column("page_progress", JSON, nullable=False), + UniqueConstraint("user", "page_number", name="wacca_bingo_uk"), + mysql_charset='utf8mb4' +) + +friend = Table( + "wacca_friend", + metadata, + Column("profile_sender", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("profile_reciever", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("is_accepted", Boolean, server_default="0"), + PrimaryKeyConstraint('profile_sender', 'profile_reciever', name='arcade_owner_pk'), + mysql_charset='utf8mb4' +) + +favorite = Table( + "wacca_favorite_song", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("song_id", Integer, nullable=False), + UniqueConstraint("user", "song_id", name="wacca_favorite_song_uk"), + mysql_charset='utf8mb4' +) + +gate = Table( + "wacca_gate", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("gate_id", Integer, nullable=False), + Column("page", Integer, nullable=False, server_default="0"), + Column("progress", Integer, nullable=False, server_default="0"), + Column("loops", Integer, nullable=False, server_default="0"), + Column("last_used", TIMESTAMP, nullable=False, server_default=func.now()), + Column("mission_flag", Integer, nullable=False, server_default="0"), + Column("total_points", Integer, nullable=False, server_default="0"), + UniqueConstraint("user", "gate_id", name="wacca_gate_uk"), +) + +class WaccaProfileData(BaseData): + def create_profile(self, aime_id: int, username: str, version: int) -> Optional[int]: + """ + Given a game version, aime id, and username, create a profile and return it's ID + """ + sql = insert(profile).values( + user=aime_id, + username=username, + version=version + ) + + conflict = sql.on_duplicate_key_update( + username = sql.inserted.username + ) + + result = self.execute(conflict) + if result is None: + self.logger.error(f"{__name__} Failed to insert wacca profile! aime id: {aime_id} username: {username}") + return None + return result.lastrowid + + def update_profile_playtype(self, profile_id: int, play_type: int, game_version: str) -> None: + sql = profile.update(profile.c.id == profile_id).values( + playcount_single = profile.c.playcount_single + 1 if play_type == 1 else profile.c.playcount_single, + + playcount_multi_vs = profile.c.playcount_multi_vs + 1 if play_type == 2 else profile.c.playcount_multi_vs, + + playcount_multi_coop = profile.c.playcount_multi_coop + 1 if play_type == 3 else profile.c.playcount_multi_coop, + + playcount_stageup = profile.c.playcount_stageup + 1 if play_type == 4 else profile.c.playcount_stageup, + + last_game_ver = game_version, + ) + + result = self.execute(sql) + if result is None: + self.logger.error(f"update_profile: failed to update profile! profile: {profile_id}") + return None + + def update_profile_lastplayed(self, profile_id: int, last_song_id: int, last_song_difficulty: int, last_folder_order: int, + last_folder_id: int, last_song_order: int) -> None: + sql = profile.update(profile.c.id == profile_id).values( + last_song_id = last_song_id, + last_song_difficulty = last_song_difficulty, + last_folder_order = last_folder_order, + last_folder_id = last_folder_id, + last_song_order = last_song_order + ) + result = self.execute(sql) + if result is None: + self.logger.error(f"update_profile_lastplayed: failed to update profile! profile: {profile_id}") + return None + + def update_profile_dan(self, profile_id: int, dan_level: int, dan_type: int) -> Optional[int]: + sql = profile.update(profile.c.id == profile_id).values( + dan_level = dan_level, + dan_type = dan_type + ) + + result = self.execute(sql) + if result is None: + self.logger.warn(f"update_profile_dan: Failed to update! profile {profile_id}") + return None + return result.lastrowid + + def get_profile(self, profile_id: int = 0, aime_id: int = None) -> Optional[Row]: + """ + Given a game version and either a profile or aime id, return the profile + """ + if aime_id is not None: + sql = profile.select(profile.c.user == aime_id) + elif profile_id > 0: + sql = profile.select(profile.c.id == profile_id) + else: + self.logger.error(f"get_profile: Bad arguments!! profile_id {profile_id} aime_id {aime_id}") + return None + + result = self.execute(sql) + if result is None: return None + return result.fetchone() + + def get_options(self, user_id: int, option_id: int = None) -> Optional[List[Row]]: + """ + Get a specific user option for a profile, or all of them if none specified + """ + sql = option.select( + and_(option.c.user == user_id, + option.c.opt_id == option_id if option_id is not None else True) + ) + + result = self.execute(sql) + if result is None: return None + if option_id is not None: + return result.fetchone() + else: + return result.fetchall() + + def update_option(self, user_id: int, option_id: int, value: int) -> Optional[int]: + sql = insert(option).values( + user = user_id, + opt_id = option_id, + value = value + ) + + conflict = sql.on_duplicate_key_update( + value = sql.inserted.value + ) + + result = self.execute(conflict) + if result is None: + self.logger.error(f"{__name__} failed to insert option! profile: {user_id}, option: {option_id}, value: {value}") + return None + + return result.lastrowid + + def add_favorite_song(self, user_id: int, song_id: int) -> Optional[int]: + sql = favorite.insert().values( + user=user_id, + song_id=song_id + ) + + result = self.execute(sql) + if result is None: + self.logger.error(f"{__name__} failed to insert favorite! profile: {user_id}, song_id: {song_id}") + return None + return result.lastrowid + + def remove_favorite_song(self, user_id: int, song_id: int) -> None: + sql = favorite.delete(and_(favorite.c.user == user_id, favorite.c.song_id == song_id)) + + result = self.execute(sql) + if result is None: + self.logger.error(f"{__name__} failed to remove favorite! profile: {user_id}, song_id: {song_id}") + return None + + def get_favorite_songs(self, user_id: int) -> Optional[List[Row]]: + sql = favorite.select(favorite.c.user == user_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def get_gates(self, user_id: int) -> Optional[List[Row]]: + sql = select(gate).where(gate.c.user == user_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def update_gate(self, user_id: int, gate_id: int, page: int, progress: int, loop: int, mission_flag: int, + total_points: int) -> Optional[int]: + sql = insert(gate).values( + user=user_id, + gate_id=gate_id, + page=page, + progress=progress, + loops=loop, + mission_flag=mission_flag, + total_points=total_points + ) + + conflict = sql.on_duplicate_key_update( + page=sql.inserted.page, + progress=sql.inserted.progress, + loops=sql.inserted.loops, + mission_flag=sql.inserted.mission_flag, + total_points=sql.inserted.total_points, + ) + + result = self.execute(conflict) + if result is None: + self.logger.error(f"{__name__} failed to update gate! user: {user_id}, gate_id: {gate_id}") + return None + return result.lastrowid + + def get_friends(self, user_id: int) -> Optional[List[Row]]: + sql = friend.select(friend.c.profile_sender == user_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def profile_to_aime_user(self, profile_id: int) -> Optional[int]: + sql = select(profile.c.user).where(profile.c.id == profile_id) + + result = self.execute(sql) + if result is None: + self.logger.info(f"profile_to_aime_user: No user found for profile {profile_id}") + return None + + this_profile = result.fetchone() + if this_profile is None: + self.logger.info(f"profile_to_aime_user: No user found for profile {profile_id}") + return None + + return this_profile['user'] + + def session_login(self, profile_id: int, is_new_day: bool, is_consec_day: bool) -> None: + # TODO: Reset consec days counter + sql = profile.update(profile.c.id == profile_id).values( + login_count = profile.c.login_count + 1, + login_count_consec = profile.c.login_count_consec + 1, + login_count_days = profile.c.login_count_days + 1 if is_new_day else profile.c.login_count_days, + login_count_days_consec = profile.c.login_count_days_consec + 1 if is_new_day and is_consec_day else profile.c.login_count_days_consec, + login_count_today = 1 if is_new_day else profile.c.login_count_today + 1, + last_login_date = func.now() + ) + + result = self.execute(sql) + if result is None: + self.logger.error(f"session_login: failed to update profile! profile: {profile_id}") + return None + + def session_logout(self, profile_id: int) -> None: + sql = profile.update(profile.c.id == id).values( + login_count_consec = 0 + ) + + result = self.execute(sql) + if result is None: + self.logger.error(f"{__name__} failed to update profile! profile: {profile_id}") + return None + + def add_xp(self, profile_id: int, xp: int) -> None: + sql = profile.update(profile.c.id == profile_id).values( + xp = profile.c.xp + xp + ) + + result = self.execute(sql) + if result is None: + self.logger.error(f"add_xp: Failed to update profile! profile_id {profile_id} xp {xp}") + return None + + def add_wp(self, profile_id: int, wp: int) -> None: + sql = profile.update(profile.c.id == profile_id).values( + wp = profile.c.wp + wp, + wp_total = profile.c.wp_total + wp, + ) + + result = self.execute(sql) + if result is None: + self.logger.error(f"add_wp: Failed to update profile! profile_id {profile_id} wp {wp}") + return None + + def spend_wp(self, profile_id: int, wp: int) -> None: + sql = profile.update(profile.c.id == profile_id).values( + wp = profile.c.wp - wp, + wp_spent = profile.c.wp_spent + wp, + ) + + result = self.execute(sql) + if result is None: + self.logger.error(f"spend_wp: Failed to update profile! profile_id {profile_id} wp {wp}") + return None + + def activate_vip(self, profile_id: int, expire_time) -> None: + sql = profile.update(profile.c.id == profile_id).values( + vip_expire_time = expire_time + ) + + result = self.execute(sql) + if result is None: + self.logger.error(f"activate_vip: Failed to update profile! profile_id {profile_id} expire_time {expire_time}") + return None + + def update_user_rating(self, profile_id: int, new_rating: int) -> None: + sql = profile.update(profile.c.id == profile_id).values( + rating = new_rating + ) + + result = self.execute(sql) + if result is None: + self.logger.error(f"update_user_rating: Failed to update profile! profile_id {profile_id} new_rating {new_rating}") + return None + + def update_bingo(self, aime_id: int, page: int, progress: int) -> Optional[int]: + sql = insert(bingo).values( + user=aime_id, + page_number=page, + page_progress=progress + ) + + conflict = sql.on_duplicate_key_update( + page_number=page, + page_progress=progress + ) + + result = self.execute(conflict) + if result is None: + self.logger.error(f"put_bingo: failed to update! aime_id: {aime_id}") + return None + return result.lastrowid + + def get_bingo(self, aime_id: int) -> Optional[List[Row]]: + sql = select(bingo).where(bingo.c.user==aime_id) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() + + def get_bingo_page(self, aime_id: int, page: Dict) -> Optional[List[Row]]: + sql = select(bingo).where(and_(bingo.c.user==aime_id, bingo.c.page_number==page)) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() + + def update_vip_time(self, profile_id: int, time_left) -> None: + sql = profile.update(profile.c.id == profile_id).values(vip_expire_time = time_left) + + result = self.execute(sql) + if result is None: + self.logger.error(f"Failed to update VIP time for profile {profile_id}") + + def update_tutorial_flags(self, profile_id: int, flags: Dict) -> None: + sql = profile.update(profile.c.id == profile_id).values(gate_tutorial_flags = flags) + + result = self.execute(sql) + if result is None: + self.logger.error(f"Failed to update tutorial flags for profile {profile_id}") \ No newline at end of file diff --git a/titles/wacca/schema/score.py b/titles/wacca/schema/score.py new file mode 100644 index 0000000..4b0c26c --- /dev/null +++ b/titles/wacca/schema/score.py @@ -0,0 +1,260 @@ +from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_ +from sqlalchemy.types import Integer, String, TIMESTAMP, JSON, Boolean +from sqlalchemy.schema import ForeignKey +from sqlalchemy.sql import func, select +from sqlalchemy.engine import Row +from sqlalchemy.dialects.mysql import insert +from typing import Optional, List, Dict, Any + +from core.data.schema import BaseData, metadata +from core.data import cached + +best_score = Table( + "wacca_score_best", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("song_id", Integer), + Column("chart_id", Integer), + Column("score", Integer), + Column("play_ct", Integer), + Column("clear_ct", Integer), + Column("missless_ct", Integer), + Column("fullcombo_ct", Integer), + Column("allmarv_ct", Integer), + Column("grade_d_ct", Integer), + Column("grade_c_ct", Integer), + Column("grade_b_ct", Integer), + Column("grade_a_ct", Integer), + Column("grade_aa_ct", Integer), + Column("grade_aaa_ct", Integer), + Column("grade_s_ct", Integer), + Column("grade_ss_ct", Integer), + Column("grade_sss_ct", Integer), + Column("grade_master_ct", Integer), + Column("grade_sp_ct", Integer), + Column("grade_ssp_ct", Integer), + Column("grade_sssp_ct", Integer), + Column("best_combo", Integer), + Column("lowest_miss_ct", Integer), + Column("rating", Integer), + UniqueConstraint("user", "song_id", "chart_id", name="wacca_score_uk"), + mysql_charset='utf8mb4' +) + +playlog = Table( + "wacca_score_playlog", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("song_id", Integer), + Column("chart_id", Integer), + Column("score", Integer), + Column("clear", Integer), + Column("grade", Integer), + Column("max_combo", Integer), + Column("marv_ct", Integer), + Column("great_ct", Integer), + Column("good_ct", Integer), + Column("miss_ct", Integer), + Column("fast_ct", Integer), + Column("late_ct", Integer), + Column("season", Integer), + Column("date_scored", TIMESTAMP, server_default=func.now()), + mysql_charset='utf8mb4' +) + +stageup = Table( + "wacca_score_stageup", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("version", Integer), + Column("stage_id", Integer), + Column("clear_status", Integer), + Column("clear_song_ct", Integer), + Column("song1_score", Integer), + Column("song2_score", Integer), + Column("song3_score", Integer), + Column("play_ct", Integer, server_default="1"), + UniqueConstraint("user", "stage_id", name="wacca_score_stageup_uk"), + mysql_charset='utf8mb4' +) + +class WaccaScoreData(BaseData): + def put_best_score(self, user_id: int, song_id: int, chart_id: int, score: int, clear: List[int], + grade: List[int], best_combo: int, lowest_miss_ct: int) -> Optional[int]: + """ + Update the user's best score for a chart + """ + while len(grade) < 13: + grade.append(0) + + sql = insert(best_score).values( + user=user_id, + song_id=song_id, + chart_id=chart_id, + score=score, + play_ct=clear[0], + clear_ct=clear[1], + missless_ct=clear[2], + fullcombo_ct=clear[3], + allmarv_ct=clear[4], + grade_d_ct=grade[0], + grade_c_ct=grade[1], + grade_b_ct=grade[2], + grade_a_ct=grade[3], + grade_aa_ct=grade[4], + grade_aaa_ct=grade[5], + grade_s_ct=grade[6], + grade_ss_ct=grade[7], + grade_sss_ct=grade[8], + grade_master_ct=grade[9], + grade_sp_ct=grade[10], + grade_ssp_ct=grade[11], + grade_sssp_ct=grade[12], + best_combo=best_combo, + lowest_miss_ct=lowest_miss_ct, + rating=0 + ) + + conflict = sql.on_duplicate_key_update( + score=score, + play_ct=clear[0], + clear_ct=clear[1], + missless_ct=clear[2], + fullcombo_ct=clear[3], + allmarv_ct=clear[4], + grade_d_ct=grade[0], + grade_c_ct=grade[1], + grade_b_ct=grade[2], + grade_a_ct=grade[3], + grade_aa_ct=grade[4], + grade_aaa_ct=grade[5], + grade_s_ct=grade[6], + grade_ss_ct=grade[7], + grade_sss_ct=grade[8], + grade_master_ct=grade[9], + grade_sp_ct=grade[10], + grade_ssp_ct=grade[11], + grade_sssp_ct=grade[12], + best_combo=best_combo, + lowest_miss_ct=lowest_miss_ct, + ) + + result = self.execute(conflict) + if result is None: + self.logger.error(f"{__name__}: failed to insert best score! profile: {user_id}, song: {song_id}, chart: {chart_id}") + return None + + return result.lastrowid + + def put_playlog(self, user_id: int, song_id: int, chart_id: int, this_score: int, clear: int, grade: int, max_combo: int, + marv_ct: int, great_ct: int, good_ct: int, miss_ct: int, fast_ct: int, late_ct: int, season: int) -> Optional[int]: + """ + Add an entry to the user's play log + """ + sql = playlog.insert().values( + user=user_id, + song_id=song_id, + chart_id=chart_id, + score=this_score, + clear=clear, + grade=grade, + max_combo=max_combo, + marv_ct=marv_ct, + great_ct=great_ct, + good_ct=good_ct, + miss_ct=miss_ct, + fast_ct=fast_ct, + late_ct=late_ct, + season=season + ) + + result = self.execute(sql) + if result is None: + self.logger.error(f"{__name__} failed to insert playlog! profile: {user_id}, song: {song_id}, chart: {chart_id}") + return None + + return result.lastrowid + + def get_best_score(self, user_id: int, song_id: int, chart_id: int) -> Optional[Row]: + sql = best_score.select( + and_(best_score.c.user == user_id, best_score.c.song_id == song_id, best_score.c.chart_id == chart_id) + ) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() + + def get_best_scores(self, user_id: int) -> Optional[List[Row]]: + sql = best_score.select( + best_score.c.user == user_id + ) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def update_song_rating(self, user_id: int, song_id: int, chart_id: int, new_rating: int) -> None: + sql = best_score.update( + and_( + best_score.c.user == user_id, + best_score.c.song_id == song_id, + best_score.c.chart_id == chart_id + )).values( + rating = new_rating + ) + + result = self.execute(sql) + if result is None: + self.logger.error(f"update_song_rating: failed to update rating! user_id: {user_id} song_id: {song_id} chart_id {chart_id} new_rating {new_rating}") + return None + + def put_stageup(self, user_id: int, version: int, stage_id: int, clear_status: int, clear_song_ct: int, score1: int, + score2: int, score3: int) -> Optional[int]: + sql = insert(stageup).values( + user = user_id, + version = version, + stage_id = stage_id, + clear_status = clear_status, + clear_song_ct = clear_song_ct, + song1_score = score1, + song2_score = score2, + song3_score = score3, + ) + + conflict = sql.on_duplicate_key_update( + clear_status = clear_status, + clear_song_ct = clear_song_ct, + song1_score = score1, + song2_score = score2, + song3_score = score3, + play_ct = stageup.c.play_ct + 1 + ) + + result = self.execute(conflict) + if result is None: + self.logger.warn(f"put_stageup: failed to update! user_id: {user_id} version: {version} stage_id: {stage_id}") + return None + return result.lastrowid + + def get_stageup(self, user_id: int, version: int) -> Optional[List[Row]]: + sql = select(stageup).where(and_(stageup.c.user==user_id, stageup.c.version==version)) + + result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def get_stageup_stage(self, user_id: int, version: int, stage_id: int) -> Optional[Row]: + sql = select(stageup).where( + and_( + stageup.c.user == user_id, + stageup.c.version == version, + stageup.c.stage_id == stage_id, + ) + ) + + result = self.execute(sql) + if result is None: return None + return result.fetchone() diff --git a/titles/wacca/schema/static.py b/titles/wacca/schema/static.py new file mode 100644 index 0000000..a5e8b41 --- /dev/null +++ b/titles/wacca/schema/static.py @@ -0,0 +1,68 @@ +from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_ +from sqlalchemy.types import Integer, String, TIMESTAMP, JSON, Boolean, Float +from sqlalchemy.schema import ForeignKey +from sqlalchemy.sql import func, select +from sqlalchemy.engine import Row +from sqlalchemy.dialects.mysql import insert +from typing import Optional, List, Dict, Any + +from core.data.schema import BaseData, metadata +from core.data import cached + +music = Table( + "wacca_static_music", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("version", Integer, nullable=False), + Column("songId", Integer), + Column("chartId", Integer), + Column("title", String(255)), + Column("artist", String(255)), + Column("bpm", String(255)), + Column("difficulty", Float), + Column("chartDesigner", String(255)), + Column("jacketFile", String(255)), + UniqueConstraint("version", "songId", "chartId", name="wacca_static_music_uk"), + mysql_charset='utf8mb4' +) + +class WaccaStaticData(BaseData): + def put_music(self, version: int, song_id: int, chart_id: int, title: str, artist: str, bpm: str, + difficulty: float, chart_designer: str, jacket: str) -> Optional[int]: + sql = insert(music).values( + version = version, + songId = song_id, + chartId = chart_id, + title = title, + artist = artist, + bpm = bpm, + difficulty = difficulty, + chartDesigner = chart_designer, + jacketFile = jacket + ) + + conflict = sql.on_duplicate_key_update( + title = title, + artist = artist, + bpm = bpm, + difficulty = difficulty, + chartDesigner = chart_designer, + jacketFile = jacket + ) + + result = self.execute(conflict) + if result is None: + self.logger.warn(f"Failed to insert music {song_id} chart {chart_id}") + return None + return result.lastrowid + + def get_music_chart(self, version: int, song_id: int, chart_id: int) -> Optional[List[Row]]: + sql = select(music).where(and_( + music.c.version == version, + music.c.songId == song_id, + music.c.chartId == chart_id + )) + + result = self.execute(sql) + if result is None: return None + return result.fetchone()