From 3825ec8e3990839e16b33edb33cf8e5b27a0d214 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sun, 19 May 2024 21:39:49 -0400 Subject: [PATCH 01/76] update contributing instructions --- contributing.md | 180 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 177 insertions(+), 3 deletions(-) diff --git a/contributing.md b/contributing.md index 5397f70..d12425a 100644 --- a/contributing.md +++ b/contributing.md @@ -1,8 +1,182 @@ # Contributing to ARTEMiS -If you would like to contribute to artemis, either by adding features, games, or fixing bugs, you can do so by forking the repo and submitting a pull request [here](https://gitea.tendokyu.moe/Hay1tsme/artemis/pulls). Please make sure, if you're submitting a PR for a game or game version, that you're following the n-0/y-1 guidelines, or it will be rejected. +If you would like to contribute to artemis, either by adding features, games, or fixing bugs, you can do so by forking the repo and submitting a pull request [here](https://gitea.tendokyu.moe/Hay1tsme/artemis/pulls). This guide assume you're familiar with both git, python, and the libraries that artemis uses. + +This document is a work in progress. If you have any questions or notice any errors, please report it to the discord. ## Adding games -Guide WIP +### Step 0 ++ Follow the "n-1" rule of thumb. PRs for game versions that are currently active in arcades will be deleted. If you're unsure, ask! ++ Always PR against the `develop` branch. ++ Check to see if somebody else is already PRing the features/games you want to add. If they are, consider contributing to them rather then making an entirely new PR. ++ We don't technically have a written code style guide (TODO) but try to keep your code consistant with code that's already there where possible. + +### Step 1 (Setup) +1) Fork the gitea repo, clone your fork, and checkout the develop branch. +2) Make a new folder in the `titles` folder, name it some recogniseable shorthand for your game (Chunithm becomes chuni, maimai dx is mai2, etc) +3) In this new folder, create a file named `__init__.py`. This is the first thing that will load when your title module is loaded by the core system, and it acts as sort of a directory for where everything lives in your module. This file will contain the following required items: + + `index`: must point to a subclass of `BaseServlet` that will handle setup and dispatching of your game. + + `game_codes`: must be a list of 4 letter SEGA game codes as strings. + + It can also contain the following optional fields: + + `database`: points to a subclass of `Data` that contains one or more subclasses of `BaseData` that act as database transaction handlers. Required for the class to store and retrieve data from the database. + + `reader`: points to a subclass of `BaseReader` that handles importing static data from game files into the database. + + `frontend`: points to a subclass of `FE_Base` that handles frontend routes for your game. + + The next step will focus on `index` + +### Step 2 (Index) +1) Create another file in your game's folder. By convention, it should be called `index.py`. + +2) Inside `index.py`, add the following code, replacing {Game name here} with the name of your game, without spaces or special characters. Look at other titles for examples. +```py +from core.title import BaseServlet +from core import CoreConfig + +class {Game name here}Servlet(BaseServlet): + def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None: + pass +``` +3) The `__init__` function should acomplish the following: + + Reading your game's config + + Setting up your games logger + + Instancing your games versions + + It's usually safe to copy and paste the `__init__` functions from other games, just make sure you change everything that needs to be changed! + +4) Go back to the `__init__.py` that you created and add the following: +```py +from .index import {Game name here}Servlet + +index = {Game name here}Servlet +``` + +5) Going back to `index.py`, within the Servlet class, define the following functions from `BaseServlet` as needed (see function documentation): + + `is_game_enabled`: Returns true if the game is enabled and should be served, false otherwise. Returns false by default, so override this to allow your game to be served. + + `get_routes`: Returns a list of Starlette routes that your game will serve. + + `get_allnet_info`: Returns a tuple of strings where the first is the allnet uri and the second is the allnet host. The function takes the game ID, version and keychip ID as parameters, so you can send different responses if need be. + + `get_mucha_info`: Only used by games that use Mucha as authentication. Returns a tuple where the first is a bool that is weather or not the game is enabled, the 2nd is a list of game CDs as strings that this servlet should handle, and the 3rd is a list of netID prefixes that each game CD should use. If your game does not use mucha, do not define this function. + + `setup`: Preforms any setup your servlet requires, such as spinning up matching servers. It is run once when the server starts. If you don't need any setup, do not define. + +6) Make sure any functions you specify to handle routes in `get_routes` are defined as async, as follows: `async def handle_thing(self, request: Request) -> Response:` where Response is whatever kind of Response class you'll be returning. Make sure all paths in this function return some subclass of Response, otherwise you'll get an error when serving. + +### Step 3 (Constants) +1) In your game's folder, create a file to store static values for your game. By convention, we call this `const.py` + +2) Inside, create a class called `{Game name here}Constants`. Do not define an `__init__` function. + +3) Put constants related to your game here. A good example of something to put here is game codes. +```py +class {Game name here}Constants: + GAME_CODE = "SBXX" + CONFIG_NAME = "{game name}.yaml" +``` + +4) If you choose to put game codes in here, add this to your `__init__.py` file: +```py +from .const import {Game name here}Constants +... +game_codes = [{Game name here}Constants.GAME_CODE] +``` + +### Step 4 (Config) +1) Make a file to store your game's config. By convention, it should be called `config.py` + +2) Inside that file, add the following: +```py +from core.config import CoreConfig + +class {game name}ServerConfig: + def __init__(self, parent_config: "{game name}Config") -> None: + self.__config = parent_config + + @property + def enable(self) -> bool: + return CoreConfig.get_config_field( + self.__config, "{game name}", "server", "enable", default=True + ) + + @property + def loglevel(self) -> int: + return CoreConfig.str_to_loglevel( + CoreConfig.get_config_field( + self.__config, "{game name}", "server", "loglevel", default="info" + ) + ) + +class {game name}Config(dict): + def __init__(self) -> None: + self.server = {game name}ServerConfig(self) +``` + +3) In the `example_config` folder, create a yaml file for your game. By convention, it should be called `{game folder name}.ymal`. Add the following: +```yaml +server: + enable: True + loglevel: "info" +``` + +4) Add any additional config options that you feel the game needs. Look to other games for config examples. + +5) In `index.py` import your config and instance it in `__init__` with: +```py +self.game_cfg = {game folder name}Config() +if path.exists(f"{cfg_dir}/{game folder name}Constants.CONFIG_NAME}"): + self.game_cfg.update( + yaml.safe_load(open(f"{cfg_dir}/{game folder name}Constants.CONFIG_NAME}")) + ) +``` +This will attempt to load the config file you specified in your constants, and if not, go with the defaults specified in `config.py`. This game_cfg object can then be passed down to your handlers when you create them. + +At this stage your game should be loaded by allnet, and serve whatever routes you put in `get_routes`. See the next section about adding versions and handlers. + +### Step 5 (Database) +TODO + +### Step 6 (Frontend) +TODO + +### Step 7 (Reader) +TODO ## Adding game versions -Guide WIP \ No newline at end of file +See the above section about code expectations and how to PR. +1) In the game's folder, create a python file to contain the version handlers. By convention, the first version is version 0, and is stored in `base.py`. Versions following that increment the version number, and are stored in `{short version name}.py`. See Wacca's folder for an example of how to name versions. + +2) Internal version numbers should be defined in `const.py`. The version should change any time the game gets a major update (i.e. a new version or plus version.) +```py +# in const.py +VERSION_{game name} = 0 +VERSION_{game name}_PLUS = 1 +``` + +3) Inside `base.py` (or whatever your version is named) add the following: +```py +class {game name}Base: + def __init__(self, cfg: CoreConfig, game_cfg: {game name}Config) -> None: + self.game_config = game_cfg + self.core_config = cfg + self.version = {game name}Constants.VERSION_{game name} + self.data = {game name}Data(cfg) + # Any other initialization stuff +``` + +4) Define your handlers. This will vary wildly by game, but best practice is to keep the naming consistant, so that the main dispatch function in `index.py` can use `getattr` to get the handler, rather then having a static list of what endpoint or request type goes to which handler. See Wacca's `index.py` and `base.py` for examples of how to do this. + +5) If your version is not the base version, make sure it inherits from the base version: +```py +class {game name}Plus({game name}Base): + def __init__(self, cfg: CoreConfig, game_cfg: {game name}Config) -> None: + super().__init__(cfg, game_cfg) + self.version = {game name}Constants.VERSION_{game name}_PLUS +``` + +6) Back in `index.py` make sure to import your new class, and add it to `__init__`. Some games may opt to just a single list called `self.versions` that contains all the version classes at their internal version's index. Others may simply define them as seperate members. See Wacca for an example of `self.versions` + +7) Add your version to your game's dispatching logic. + +8) Test to make sure your game is being handled properly. + +9) Submit a PR. + +## Adding/improving core services +If you intend to submit improvements or additions to core services (allnet, mucha, billing, aimedb, database, etc) please get in touch with a maintainer. From 3ed8d9c16b0a87d950e4fbcc320126a695564bec Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sun, 19 May 2024 21:40:02 -0400 Subject: [PATCH 02/76] update get_mucha_info documentation --- core/title.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/title.py b/core/title.py index 7aeb433..016e09a 100644 --- a/core/title.py +++ b/core/title.py @@ -80,7 +80,8 @@ class BaseServlet: cfg_dir (str): Config directory Returns: - Tuple[bool, str]: Tuple where offset 0 is true if the game is enabled, false otherwise, and offset 1 is the game CD + Tuple[bool, List[str], List[str]]: Tuple where offset 0 is true if the game is enabled, false otherwise, and offset 1 is the game CDs handled + by this servlette, and offset 2 is mucha netID prefixes that should be used for each game CD. """ return (False, [], []) From 70b40ce99284587d110ee5fd8fa269da2f2409ae Mon Sep 17 00:00:00 2001 From: roaz Date: Tue, 21 May 2024 04:07:17 +0000 Subject: [PATCH 03/76] chuni: Fix endpoint for older version of SDGS (#141) Reviewed-on: https://gitea.tendokyu.moe/Hay1tsme/artemis/pulls/141 Co-authored-by: roaz Co-committed-by: roaz --- readme.md | 1 + titles/chuni/index.py | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/readme.md b/readme.md index 114fef5..505f241 100644 --- a/readme.md +++ b/readme.md @@ -10,6 +10,7 @@ Games listed below have been tested and confirmed working. Only game versions ol + CHUNITHM INTL + SUPERSTAR + + SUPERSTAR PLUS + NEW + NEW PLUS + SUN diff --git a/titles/chuni/index.py b/titles/chuni/index.py index f781898..f0f1eac 100644 --- a/titles/chuni/index.py +++ b/titles/chuni/index.py @@ -195,11 +195,11 @@ class ChuniServlet(BaseServlet): internal_ver = ChuniConstants.VER_CHUNITHM_NEW_PLUS elif version >= 210 and version < 215: # SUN internal_ver = ChuniConstants.VER_CHUNITHM_SUN - elif version >= 215: # SUN + elif version >= 215: # SUN PLUS internal_ver = ChuniConstants.VER_CHUNITHM_SUN_PLUS elif game_code == "SDGS": # Int - if version < 110: # SUPERSTAR - internal_ver = ChuniConstants.VER_CHUNITHM_PARADISE # FIXME: Not sure what was intended to go here? was just "PARADISE" + if version < 110: # SUPERSTAR / SUPERSTAR PLUS + internal_ver = ChuniConstants.VER_CHUNITHM_PARADISE # SUPERSTAR / SUPERSTAR PLUS worked fine with it elif version >= 110 and version < 115: # NEW internal_ver = ChuniConstants.VER_CHUNITHM_NEW elif version >= 115 and version < 120: # NEW PLUS!! @@ -272,11 +272,13 @@ class ChuniServlet(BaseServlet): self.logger.info(f"v{version} {endpoint} request from {client_ip}") self.logger.debug(req_data) - endpoint = ( - endpoint.replace("C3Exp", "") - if game_code == "SDGS" - else endpoint - ) + if game_code == "SDGS" and version >= 110: + endpoint = endpoint.replace("C3Exp", "") + elif game_code == "SDGS" and version < 110: + endpoint = endpoint.replace("Exp", "") + else: + endpoint = endpoint + func_to_find = "handle_" + inflection.underscore(endpoint) + "_request" handler_cls = self.versions[internal_ver](self.core_cfg, self.game_cfg) From a9587a9c917ac79862be5f8c3dc5b6bc74813ff0 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Tue, 21 May 2024 23:05:22 -0400 Subject: [PATCH 04/76] add additional details to event_log --- .../2bf9f38d9444_add_event_log_info.py | 48 +++++++++++++++++++ core/data/schema/base.py | 13 ++++- 2 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 core/data/alembic/versions/2bf9f38d9444_add_event_log_info.py diff --git a/core/data/alembic/versions/2bf9f38d9444_add_event_log_info.py b/core/data/alembic/versions/2bf9f38d9444_add_event_log_info.py new file mode 100644 index 0000000..5c2492d --- /dev/null +++ b/core/data/alembic/versions/2bf9f38d9444_add_event_log_info.py @@ -0,0 +1,48 @@ +"""add_event_log_info + +Revision ID: 2bf9f38d9444 +Revises: 81e44dd6047a +Create Date: 2024-05-21 23:00:17.468407 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = '2bf9f38d9444' +down_revision = '81e44dd6047a' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('event_log', sa.Column('user', sa.INTEGER(), nullable=True)) + op.add_column('event_log', sa.Column('arcade', sa.INTEGER(), nullable=True)) + op.add_column('event_log', sa.Column('machine', sa.INTEGER(), nullable=True)) + op.add_column('event_log', sa.Column('ip', sa.TEXT(length=39), nullable=True)) + op.alter_column('event_log', 'when_logged', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('now()'), + existing_nullable=False) + op.create_foreign_key(None, 'event_log', 'machine', ['machine'], ['id'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'event_log', 'arcade', ['arcade'], ['id'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'event_log', 'aime_user', ['user'], ['id'], onupdate='cascade', ondelete='cascade') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'event_log', type_='foreignkey') + op.drop_constraint(None, 'event_log', type_='foreignkey') + op.drop_constraint(None, 'event_log', type_='foreignkey') + op.alter_column('event_log', 'when_logged', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('current_timestamp()'), + existing_nullable=False) + op.drop_column('event_log', 'ip') + op.drop_column('event_log', 'machine') + op.drop_column('event_log', 'arcade') + op.drop_column('event_log', 'user') + # ### end Alembic commands ### diff --git a/core/data/schema/base.py b/core/data/schema/base.py index 55eceaf..a87ce00 100644 --- a/core/data/schema/base.py +++ b/core/data/schema/base.py @@ -8,7 +8,8 @@ from sqlalchemy.engine.base import Connection from sqlalchemy.sql import text, func, select from sqlalchemy.exc import SQLAlchemyError from sqlalchemy import MetaData, Table, Column -from sqlalchemy.types import Integer, String, TIMESTAMP, JSON +from sqlalchemy.types import Integer, String, TIMESTAMP, JSON, INTEGER, TEXT +from sqlalchemy.schema import ForeignKey from sqlalchemy.dialects.mysql import insert from core.config import CoreConfig @@ -22,6 +23,10 @@ event_log = Table( Column("system", String(255), nullable=False), Column("type", String(255), nullable=False), Column("severity", Integer, nullable=False), + Column("user", INTEGER, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")), + Column("arcade", INTEGER, ForeignKey("arcade.id", ondelete="cascade", onupdate="cascade")), + Column("machine", INTEGER, ForeignKey("machine.id", ondelete="cascade", onupdate="cascade")), + Column("ip", TEXT(39)), Column("message", String(1000), nullable=False), Column("details", JSON, nullable=False), Column("when_logged", TIMESTAMP, nullable=False, server_default=func.now()), @@ -75,12 +80,16 @@ class BaseData: return randrange(10000, 9999999) async def log_event( - self, system: str, type: str, severity: int, message: str, details: Dict = {} + self, system: str, type: str, severity: int, message: str, details: Dict = {}, user: int = None, arcade: int = None, machine: int = None, ip: str = None ) -> Optional[int]: sql = event_log.insert().values( system=system, type=type, severity=severity, + user=user, + arcade=arcade, + machine=machine, + ip=ip, message=message, details=json.dumps(details), ) From 4ee4c26f5e5161dc789eaac3379bba610182f1ee Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Wed, 22 May 2024 00:05:32 -0400 Subject: [PATCH 05/76] allnet: enhance logging --- core/allnet.py | 113 ++++++++++++++---- ...2d024cf145a1_add_event_log_game_version.py | 46 +++++++ core/data/schema/base.py | 7 +- 3 files changed, 142 insertions(+), 24 deletions(-) create mode 100644 core/data/alembic/versions/2d024cf145a1_add_event_log_game_version.py diff --git a/core/allnet.py b/core/allnet.py index b03d168..861a603 100644 --- a/core/allnet.py +++ b/core/allnet.py @@ -171,7 +171,7 @@ class AllnetServlet: if machine is None and not self.config.server.allow_unregistered_serials: msg = f"Unrecognised serial {req.serial} attempted allnet auth from {request_ip}." await self.data.base.log_event( - "allnet", "ALLNET_AUTH_UNKNOWN_SERIAL", logging.WARN, msg + "allnet", "ALLNET_AUTH_UNKNOWN_SERIAL", logging.WARN, msg, {"serial": req.serial}, None, None, None, request_ip, req.game_id, req.ver ) self.logger.warning(msg) @@ -183,9 +183,9 @@ class AllnetServlet: arcade = await self.data.arcade.get_arcade(machine["arcade"]) if self.config.server.check_arcade_ip: if arcade["ip"] and arcade["ip"] is not None and arcade["ip"] != req.ip: - msg = f"Serial {req.serial} attempted allnet auth from bad IP {req.ip} (expected {arcade['ip']})." + msg = f"{req.serial} attempted allnet auth from bad IP {req.ip} (expected {arcade['ip']})." await self.data.base.log_event( - "allnet", "ALLNET_AUTH_BAD_IP", logging.ERROR, msg + "allnet", "ALLNET_AUTH_BAD_IP", logging.ERROR, msg, {}, None, arcade['id'], machine['id'], request_ip, req.game_id, req.ver ) self.logger.warning(msg) @@ -194,9 +194,9 @@ class AllnetServlet: return PlainTextResponse(urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n") elif (not arcade["ip"] or arcade["ip"] is None) and self.config.server.strict_ip_checking: - msg = f"Serial {req.serial} attempted allnet auth from bad IP {req.ip}, but arcade {arcade['id']} has no IP set! (strict checking enabled)." + msg = f"{req.serial} attempted allnet auth from bad IP {req.ip}, but arcade {arcade['id']} has no IP set! (strict checking enabled)." await self.data.base.log_event( - "allnet", "ALLNET_AUTH_NO_SHOP_IP", logging.ERROR, msg + "allnet", "ALLNET_AUTH_NO_SHOP_IP", logging.ERROR, msg, {}, None, arcade['id'], machine['id'], request_ip, req.game_id, req.ver ) self.logger.warning(msg) @@ -204,7 +204,17 @@ class AllnetServlet: resp_dict = {k: v for k, v in vars(resp).items() if v is not None} return PlainTextResponse(urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n") + if machine['game'] and machine['game'] != req.game_id: + msg = f"{req.serial} attempted allnet auth with bad game ID {req.game_id} (expected {machine['game']})." + await self.data.base.log_event( + "allnet", "ALLNET_AUTH_BAD_GAME", logging.ERROR, msg, {}, None, arcade['id'], machine['id'], request_ip, req.game_id, req.ver + ) + self.logger.warning(msg) + resp.stat = ALLNET_STAT.bad_game.value + resp_dict = {k: v for k, v in vars(resp).items() if v is not None} + return PlainTextResponse(urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n") + country = ( arcade["country"] if machine["country"] is None else machine["country"] ) @@ -236,11 +246,14 @@ class AllnetServlet: arcade["timezone"] if arcade["timezone"] is not None else "+0900" if req.format_ver == 3 else "+09:00" ) + else: + arcade = None + if req.game_id not in TitleServlet.title_registry: if not self.config.server.is_develop: msg = f"Unrecognised game {req.game_id} attempted allnet auth from {request_ip}." await self.data.base.log_event( - "allnet", "ALLNET_AUTH_UNKNOWN_GAME", logging.WARN, msg + "allnet", "ALLNET_AUTH_UNKNOWN_GAME", logging.WARN, msg, {}, None, arcade['id'] if arcade else None, machine['id'] if machine else None, request_ip, req.game_id, req.ver ) self.logger.warning(msg) @@ -271,8 +284,17 @@ class AllnetServlet: resp_dict = {k: v for k, v in vars(resp).items() if v is not None} return PlainTextResponse(urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n") - msg = f"{req.serial} authenticated from {request_ip}: {req.game_id} v{req.ver}" - await self.data.base.log_event("allnet", "ALLNET_AUTH_SUCCESS", logging.INFO, msg) + if machine and arcade: + msg = f"{req.serial} authenticated from {request_ip}: {req.game_id} v{req.ver}" + await self.data.base.log_event( + "allnet", "ALLNET_AUTH_SUCCESS", logging.INFO, msg, {}, None, arcade['id'], machine['id'], request_ip, req.game_id, req.ver + ) + else: + msg = f"Allow unregistered serial {req.serial} to authenticate from {request_ip}: {req.game_id} v{req.ver}" + await self.data.base.log_event( + "allnet", "ALLNET_AUTH_SUCCESS_UNREG", logging.INFO, msg, {"serial": req.serial}, None, None, None, request_ip, req.game_id, req.ver + ) + self.logger.info(msg) resp_dict = {k: v for k, v in vars(resp).items() if v is not None} @@ -329,7 +351,11 @@ class AllnetServlet: ): return PlainTextResponse(urllib.parse.unquote(urllib.parse.urlencode(vars(resp))) + "\n") - else: # TODO: Keychip check + else: + machine = await self.data.arcade.get_machine(req.serial) + if not machine or not machine['ota_enable'] or not machine['is_cab'] or machine['is_blacklisted']: + return PlainTextResponse(urllib.parse.unquote(urllib.parse.urlencode(vars(resp))) + "\n") + if path.exists( f"{self.config.allnet.update_cfg_folder}/{req.game_id}-{req.ver.replace('.', '')}-app.ini" ): @@ -340,8 +366,13 @@ class AllnetServlet: ): resp.uri += f"|http://{self.config.server.hostname}:{self.config.server.port}/dl/ini/{req.game_id}-{req.ver.replace('.', '')}-opt.ini" - self.logger.debug(f"Sending download uri {resp.uri}") - await self.data.base.log_event("allnet", "DLORDER_REQ_SUCCESS", logging.INFO, f"{Utils.get_ip_addr(request)} requested DL Order for {req.serial} {req.game_id} v{req.ver}") + if resp.uri: + self.logger.info(f"Sending download uri {resp.uri}") + await self.data.base.log_event( + "allnet", "DLORDER_REQ_SUCCESS", logging.INFO, f"Send download URI to {req.serial} for {req.game_id} v{req.ver} from {Utils.get_ip_addr(request)}", {"uri": resp.uri}, None, + machine['arcade'], machine['id'], request_ip, req.game_id, req.ver + ) + # Maybe add a log event for checkin but no url sent? res_str = urllib.parse.unquote(urllib.parse.urlencode(vars(resp))) + "\n" @@ -357,13 +388,16 @@ class AllnetServlet: async def handle_dlorder_ini(self, request: Request) -> bytes: req_file = request.path_params.get("file", "").replace("%0A", "").replace("\n", "") + request_ip = Utils.get_ip_addr(request) if not req_file: return PlainTextResponse(status_code=404) if path.exists(f"{self.config.allnet.update_cfg_folder}/{req_file}"): - self.logger.info(f"Request for DL INI file {req_file} from {Utils.get_ip_addr(request)} successful") - await self.data.base.log_event("allnet", "DLORDER_INI_SENT", logging.INFO, f"{Utils.get_ip_addr(request)} successfully recieved {req_file}") + self.logger.info(f"Request for DL INI file {req_file} from {request_ip} successful") + await self.data.base.log_event( + "allnet", "DLORDER_INI_SENT", logging.INFO, f"{request_ip} successfully recieved {req_file}", {"file": req_file}, ip=request_ip + ) return PlainTextResponse(open( f"{self.config.allnet.update_cfg_folder}/{req_file}", "r", encoding="utf-8" @@ -401,7 +435,13 @@ class AllnetServlet: msg = f"{rep.serial} @ {client_ip} reported {rep.rep_type.name} download state {rep.rf_state.name} for {rep.gd} v{rep.dav}:"\ f" {rep.tdsc}/{rep.tsc} segments downloaded for working files {rep.wfl} with {rep.dfl if rep.dfl else 'none'} complete." - await self.data.base.log_event("allnet", "DL_REPORT", logging.INFO, msg, dl_data) + machine = await self.data.arcade.get_machine(rep.serial) + if machine: + await self.data.base.log_event("allnet", "DL_REPORT", logging.INFO, msg, dl_data, None, machine['arcade'], machine['id'], client_ip, rep.gd, rep.dav) + + else: + msg = "Unknown serial " + msg + await self.data.base.log_event("allnet", "DL_REPORT_UNREG", logging.INFO, msg, dl_data, None, None, None, client_ip, rep.gd, rep.dav) self.logger.info(msg) return PlainTextResponse("OK") @@ -421,14 +461,24 @@ class AllnetServlet: if serial is None or num_files_dld is None or num_files_to_dl is None or dl_state is None: return PlainTextResponse("NG") - self.logger.info(f"LoaderStateRecorder Request from {ip} {serial}: {num_files_dld}/{num_files_to_dl} Files download (State: {dl_state})") + msg = f"LoaderStateRecorder Request from {ip} {serial}: {num_files_dld}/{num_files_to_dl} Files download (State: {dl_state})" + machine = await self.data.arcade.get_machine(serial) + if machine: + await self.data.base.log_event("allnet", "LSR_REPORT", logging.INFO, msg, req_dict, None, machine['arcade'], machine['id'], ip) + + else: + msg = "Unregistered " + msg + await self.data.base.log_event("allnet", "LSR_REPORT_UNREG", logging.INFO, msg, req_dict, None, None, None, ip) + + self.logger.info(msg) return PlainTextResponse("OK") async def handle_alive(self, request: Request) -> bytes: return PlainTextResponse("OK") async def handle_naomitest(self, request: Request) -> bytes: - self.logger.info(f"Ping from {Utils.get_ip_addr(request)}") + # This could be spam-able, removing + #self.logger.info(f"Ping from {Utils.get_ip_addr(request)}") return PlainTextResponse("naomi ok") def allnet_req_to_dict(self, data: str) -> Optional[List[Dict[str, Any]]]: @@ -558,18 +608,35 @@ class BillingServlet: if machine is None and not self.config.server.allow_unregistered_serials: msg = f"Unrecognised serial {req.keychipid} attempted billing checkin from {request_ip} for {req.gameid} v{req.gamever}." await self.data.base.log_event( - "allnet", "BILLING_CHECKIN_NG_SERIAL", logging.WARN, msg + "allnet", "BILLING_CHECKIN_NG_SERIAL", logging.WARN, msg, ip=request_ip, game=req.gameid, version=req.gamever ) self.logger.warning(msg) return PlainTextResponse(f"result=1&requestno={req.requestno}&message=Keychip Serial bad\r\n") + + log_details = { + "playcount": req.playcnt, + "billing_type": req.billingtype.name, + "nearfull": req.nearfull, + "playlimit": req.playlimit, + } + + if machine is not None: + await self.data.base.log_event("billing", "BILLING_CHECKIN_OK", logging.INFO, "", log_details, None, machine['arcade'], machine['id'], request_ip, req.gameid, req.gamever) + + self.logger.info( + f"Unregistered Billing checkin from {request_ip}: game {req.gameid} ver {req.gamever} keychip {req.keychipid} playcount " + f"{req.playcnt} billing_type {req.billingtype.name} nearfull {req.nearfull} playlimit {req.playlimit}" + ) + else: + log_details['serial'] = req.keychipid + await self.data.base.log_event("billing", "BILLING_CHECKIN_OK_UNREG", logging.INFO, "", log_details, None, None, None, request_ip, req.gameid, req.gamever) + + self.logger.info( + f"Unregistered Billing checkin from {request_ip}: game {req.gameid} ver {req.gamever} keychip {req.keychipid} playcount " + f"{req.playcnt} billing_type {req.billingtype.name} nearfull {req.nearfull} playlimit {req.playlimit}" + ) - msg = ( - f"Billing checkin from {request_ip}: game {req.gameid} ver {req.gamever} keychip {req.keychipid} playcount " - f"{req.playcnt} billing_type {req.billingtype.name} nearfull {req.nearfull} playlimit {req.playlimit}" - ) - self.logger.info(msg) - await self.data.base.log_event("billing", "BILLING_CHECKIN_OK", logging.INFO, msg) if req.traceleft > 0: self.logger.warn(f"{req.traceleft} unsent tracelogs") kc_playlimit = req.playlimit diff --git a/core/data/alembic/versions/2d024cf145a1_add_event_log_game_version.py b/core/data/alembic/versions/2d024cf145a1_add_event_log_game_version.py new file mode 100644 index 0000000..b947763 --- /dev/null +++ b/core/data/alembic/versions/2d024cf145a1_add_event_log_game_version.py @@ -0,0 +1,46 @@ +"""add_event_log_game_version + +Revision ID: 2d024cf145a1 +Revises: 2bf9f38d9444 +Create Date: 2024-05-21 23:41:31.445331 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = '2d024cf145a1' +down_revision = '2bf9f38d9444' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('event_log', sa.Column('game', sa.TEXT(length=4), nullable=True)) + op.add_column('event_log', sa.Column('version', sa.TEXT(length=24), nullable=True)) + op.alter_column('event_log', 'ip', + existing_type=mysql.TINYTEXT(), + type_=sa.TEXT(length=39), + existing_nullable=True) + op.alter_column('event_log', 'when_logged', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('now()'), + existing_nullable=False) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('event_log', 'when_logged', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('current_timestamp()'), + existing_nullable=False) + op.alter_column('event_log', 'ip', + existing_type=sa.TEXT(length=39), + type_=mysql.TINYTEXT(), + existing_nullable=True) + op.drop_column('event_log', 'version') + op.drop_column('event_log', 'game') + # ### end Alembic commands ### diff --git a/core/data/schema/base.py b/core/data/schema/base.py index a87ce00..a6fd68d 100644 --- a/core/data/schema/base.py +++ b/core/data/schema/base.py @@ -27,6 +27,8 @@ event_log = Table( Column("arcade", INTEGER, ForeignKey("arcade.id", ondelete="cascade", onupdate="cascade")), Column("machine", INTEGER, ForeignKey("machine.id", ondelete="cascade", onupdate="cascade")), Column("ip", TEXT(39)), + Column("game", TEXT(4)), + Column("version", TEXT(24)), Column("message", String(1000), nullable=False), Column("details", JSON, nullable=False), Column("when_logged", TIMESTAMP, nullable=False, server_default=func.now()), @@ -80,7 +82,8 @@ class BaseData: return randrange(10000, 9999999) async def log_event( - self, system: str, type: str, severity: int, message: str, details: Dict = {}, user: int = None, arcade: int = None, machine: int = None, ip: str = None + self, system: str, type: str, severity: int, message: str, details: Dict = {}, user: int = None, + arcade: int = None, machine: int = None, ip: str = None, game: str = None, version: str = None ) -> Optional[int]: sql = event_log.insert().values( system=system, @@ -90,6 +93,8 @@ class BaseData: arcade=arcade, machine=machine, ip=ip, + game=game, + version=version, message=message, details=json.dumps(details), ) From e66ae9174055b24b404c184663ebf819408ccd4c Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Wed, 22 May 2024 01:36:41 -0400 Subject: [PATCH 06/76] add basic event log viewer --- core/data/schema/base.py | 4 +- core/frontend.py | 30 ++++ core/templates/sys/index.jinja | 3 + core/templates/sys/logs.jinja | 176 ++++++++++++++++++++++++ core/templates/widgets/err_banner.jinja | 2 + 5 files changed, 213 insertions(+), 2 deletions(-) create mode 100644 core/templates/sys/logs.jinja diff --git a/core/data/schema/base.py b/core/data/schema/base.py index a6fd68d..e315964 100644 --- a/core/data/schema/base.py +++ b/core/data/schema/base.py @@ -108,8 +108,8 @@ class BaseData: return result.lastrowid - async def get_event_log(self, entries: int = 100) -> Optional[List[Dict]]: - sql = event_log.select().limit(entries).all() + async def get_event_log(self, entries: int = 100) -> Optional[List[Row]]: + sql = event_log.select().limit(entries) result = await self.execute(sql) if result is None: diff --git a/core/frontend.py b/core/frontend.py index 382a082..bb3e9aa 100644 --- a/core/frontend.py +++ b/core/frontend.py @@ -133,6 +133,7 @@ class FrontendServlet(): ]), Mount("/sys", routes=[ Route("/", self.system.render_GET, methods=['GET']), + Route("/logs", self.system.render_logs, methods=['GET']), Route("/lookup.user", self.system.lookup_user, methods=['GET']), Route("/lookup.shop", self.system.lookup_shop, methods=['GET']), Route("/add.user", self.system.add_user, methods=['POST']), @@ -783,6 +784,35 @@ class FE_System(FE_Base): cabadd={"id": cab_id, "serial": serial}, ), media_type="text/html; charset=utf-8") + async def render_logs(self, request: Request): + template = self.environment.get_template("core/templates/sys/logs.jinja") + events = [] + + usr_sesh = self.validate_session(request) + if not usr_sesh or not self.test_perm(usr_sesh.permissions, PermissionOffset.SYSADMIN): + return RedirectResponse("/sys/?e=11", 303) + + logs = await self.data.base.get_event_log() + if not logs: + logs = [] + + for log in logs: + evt = log._asdict() + if not evt['user']: evt["user"] = "NONE" + if not evt['arcade']: evt["arcade"] = "NONE" + if not evt['machine']: evt["machine"] = "NONE" + if not evt['ip']: evt["ip"] = "NONE" + if not evt['game']: evt["game"] = "NONE" + if not evt['version']: evt["version"] = "NONE" + evt['when_logged'] = evt['when_logged'].strftime("%x %X") + events.append(evt) + + return Response(template.render( + title=f"{self.core_config.server.name} | Event Logs", + sesh=vars(usr_sesh), + events=events + ), media_type="text/html; charset=utf-8") + class FE_Arcade(FE_Base): async def render_GET(self, request: Request): template = self.environment.get_template("core/templates/arcade/index.jinja") diff --git a/core/templates/sys/index.jinja b/core/templates/sys/index.jinja index b589d90..637b8c9 100644 --- a/core/templates/sys/index.jinja +++ b/core/templates/sys/index.jinja @@ -51,6 +51,9 @@ + {% endif %}
diff --git a/core/templates/sys/logs.jinja b/core/templates/sys/logs.jinja new file mode 100644 index 0000000..3f87fb8 --- /dev/null +++ b/core/templates/sys/logs.jinja @@ -0,0 +1,176 @@ +{% extends "core/templates/index.jinja" %} +{% block content %} +

Event Logs

+ + + + + + + + + + + + + + + + +
Viewing last 100 logs
SeveritySystemNameUserArcadeMachineGameVersionMessageParams
+
+ +  + + +
+ +{% endblock content %} \ No newline at end of file diff --git a/core/templates/widgets/err_banner.jinja b/core/templates/widgets/err_banner.jinja index eec204a..25208df 100644 --- a/core/templates/widgets/err_banner.jinja +++ b/core/templates/widgets/err_banner.jinja @@ -21,6 +21,8 @@ New Nickname too long You must be logged in to preform this action {% elif error == 10 %} Invalid serial number +{% elif error == 11 %} +Access Denied {% else %} An unknown error occoured {% endif %} From 0a408baa87f2ee31f99c3ca4d4667f994f67f3fb Mon Sep 17 00:00:00 2001 From: Midorica Date: Thu, 23 May 2024 09:21:08 -0400 Subject: [PATCH 07/76] DIVA: Fixed binary handler & render_POST errors --- changelog.md | 4 ++++ titles/diva/index.py | 15 ++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/changelog.md b/changelog.md index 42c7cfe..a427f2f 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,10 @@ # Changelog Documenting updates to ARTEMiS, to be updated every time the master branch is pushed to. +## 20240523 +### DIVA ++ Fixed binary handler & render_POST errors + ## 20240408 ### System + Modified the game specific documentation diff --git a/titles/diva/index.py b/titles/diva/index.py index f414ab2..01e5eeb 100644 --- a/titles/diva/index.py +++ b/titles/diva/index.py @@ -79,7 +79,7 @@ class DivaServlet(BaseServlet): return True - async def render_POST(self, request: Request, game_code: str, matchers: Dict) -> bytes: + async def render_POST(self, request: Request) -> bytes: req_raw = await request.body() url_header = request.headers @@ -98,8 +98,17 @@ class DivaServlet(BaseServlet): 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) + try: + handler = getattr(self.base, f"handle_{bin_req_data['cmd']}_request") + resp = handler(bin_req_data) + + except AttributeError as e: + self.logger.warning(f"Unhandled {bin_req_data['cmd']} request {e}") + return PlainTextResponse(f"cmd={bin_req_data['cmd']}&req_id={bin_req_data['req_id']}&stat=ok") + + except Exception as e: + self.logger.error(f"Error handling method {e}") + return PlainTextResponse(f"cmd={bin_req_data['cmd']}&req_id={bin_req_data['req_id']}&stat=ok") self.logger.debug( f"Response cmd={bin_req_data['cmd']}&req_id={bin_req_data['req_id']}&stat=ok{resp}" From a5fd6e65d6dc9a8a68a46b3a86f3b9d2344a5910 Mon Sep 17 00:00:00 2001 From: Midorica Date: Fri, 24 May 2024 10:13:04 -0400 Subject: [PATCH 08/76] diva: fix handle_start_request --- changelog.md | 4 ++++ titles/diva/base.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index a427f2f..51971ba 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,10 @@ # Changelog Documenting updates to ARTEMiS, to be updated every time the master branch is pushed to. +## 20240524 +### DIVA ++ Fixed new profile start request causing coroutine error + ## 20240523 ### DIVA + Fixed binary handler & render_POST errors diff --git a/titles/diva/base.py b/titles/diva/base.py index 7fe3fb5..3b96848 100644 --- a/titles/diva/base.py +++ b/titles/diva/base.py @@ -431,7 +431,7 @@ class DivaBase: profile = await self.data.profile.get_profile(data["pd_id"], self.version) profile_shop = await self.data.item.get_shop(data["pd_id"], self.version) if profile is None: - return + return {} mdl_have = "F" * 250 # generate the mdl_have string if "unlock_all_modules" is disabled From 31ce293a8c2f1ad925905bd828c8d7180262abbd Mon Sep 17 00:00:00 2001 From: ThatzOkay Date: Sun, 26 May 2024 15:58:28 +0200 Subject: [PATCH 09/76] Fix missing await when starting diva profile --- changelog.md | 3 +++ titles/diva/schema/customize.py | 2 +- titles/diva/schema/module.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index 51971ba..0e8b39a 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,9 @@ # Changelog Documenting updates to ARTEMiS, to be updated every time the master branch is pushed to. +## 20240526 ++ Fixed missing awaits causing coroutine error + ## 20240524 ### DIVA + Fixed new profile start request causing coroutine error diff --git a/titles/diva/schema/customize.py b/titles/diva/schema/customize.py index 838a7d2..21ccf7d 100644 --- a/titles/diva/schema/customize.py +++ b/titles/diva/schema/customize.py @@ -54,7 +54,7 @@ class DivaCustomizeItemData(BaseData): Given a game version and an aime id, return the cstmz_itm_have hex string required for diva directly """ - items_list = self.get_customize_items(aime_id, version) + items_list = await self.get_customize_items(aime_id, version) if items_list is None: items_list = [] item_have = 0 diff --git a/titles/diva/schema/module.py b/titles/diva/schema/module.py index a6286ee..de091b1 100644 --- a/titles/diva/schema/module.py +++ b/titles/diva/schema/module.py @@ -50,7 +50,7 @@ class DivaModuleData(BaseData): Given a game version and an aime id, return the mdl_have hex string required for diva directly """ - module_list = self.get_modules(aime_id, version) + module_list = await self.get_modules(aime_id, version) if module_list is None: module_list = [] module_have = 0 From 12f035b7e5351f1ce20da689239af7534ed7229c Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Sun, 26 May 2024 22:49:05 -0400 Subject: [PATCH 10/76] add no logs message to event viewer --- core/templates/sys/logs.jinja | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/templates/sys/logs.jinja b/core/templates/sys/logs.jinja index 3f87fb8..3dfe531 100644 --- a/core/templates/sys/logs.jinja +++ b/core/templates/sys/logs.jinja @@ -17,6 +17,11 @@ Params + {% if events is not defined or events|length == 0 %} + + No Events + + {% endif %}
+
+ +
20 Digit access code from a card registered to your account, or your account username. (NOT your username from a game!)

- + +
Leave blank if registering for the webui. Your card must have been used on a game connected to this server to register.

-
*To register for the webui, type in the access code of your card, as shown in a game, and leave the password field blank.
-
*If you have not registered a card with this server, you cannot create a webui account.
{% endblock content %} \ No newline at end of file From 123ec99a97d4f7045a082a01d9a15b2e27c6f9de Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sat, 8 Jun 2024 20:55:41 -0400 Subject: [PATCH 19/76] mai2: fix aggressive find and replace --- titles/mai2/schema/profile.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/titles/mai2/schema/profile.py b/titles/mai2/schema/profile.py index 97dcaad..d45ee16 100644 --- a/titles/mai2/schema/profile.py +++ b/titles/mai2/schema/profile.py @@ -187,14 +187,14 @@ ghost = Table( Column("shopId", Integer), Column("regionCode", Integer), Column("typeId", Integer), - Column("rival", Integer), + Column("musicId", Integer), Column("difficulty", Integer), Column("version", Integer), Column("resultBitList", JSON), Column("resultNum", Integer), Column("achievement", Integer), UniqueConstraint( - "user", "version", "rival", "difficulty", name="mai2_profile_ghost_uk" + "user", "version", "musicId", "difficulty", name="mai2_profile_ghost_uk" ), mysql_charset="utf8mb4", ) @@ -209,7 +209,7 @@ extend = Table( nullable=False, ), Column("version", Integer, nullable=False), - Column("selectrival", Integer), + Column("selectMusicId", Integer), Column("selectDifficultyId", Integer), Column("categoryIndex", Integer), Column("musicIndex", Integer), @@ -239,7 +239,7 @@ option = Table( nullable=False, ), Column("version", Integer, nullable=False), - Column("selectrival", Integer), + Column("selectMusicId", Integer), Column("optionKind", Integer), Column("noteSpeed", Integer), Column("slideSpeed", Integer), From eccbd1ad812a02a1e193a6186b7fbeab879554b7 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sat, 8 Jun 2024 21:25:48 -0400 Subject: [PATCH 20/76] mai2: add rivals support --- titles/mai2/base.py | 58 +++++++++++++++++++++++++++++++++++++ titles/mai2/schema/score.py | 8 ++--- 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/titles/mai2/base.py b/titles/mai2/base.py index b2386c1..974c94c 100644 --- a/titles/mai2/base.py +++ b/titles/mai2/base.py @@ -942,6 +942,64 @@ class Mai2Base: "itemId": -1, } + async def handle_get_user_rival_data_api_request(self, data: Dict) -> Dict: + user_id = data.get("userId", 0) + rival_id = data.get("rivalId", 0) + + if not user_id or not rival_id: return {} + + rival_pf = await self.data.profile.get_profile_detail(rival_id) + if not rival_pf: return {} + + return { + "userId": user_id, + "userRivalData": { + "rivalId": rival_id, + "rivalName": rival_pf['userName'] + } + } + + async def handle_get_user_rival_music_api_request(self, data: Dict) -> Dict: + user_id = data.get("userId", 0) + rival_id = data.get("rivalId", 0) + next_index = data.get("nextIndex", 0) + max_ct = 100 + upper_lim = next_index + max_ct + rival_music_list: Dict[int, List] = {} + + songs = await self.data.score.get_best_scores(rival_id) + if songs is None: + self.logger.debug("handle_get_user_rival_music_api_request: get_best_scores returned None!") + return { + "userId": user_id, + "rivalId": rival_id, + "nextIndex": 0, + "userRivalMusicList": [] # musicId userRivalMusicDetailList -> level achievement deluxscoreMax + } + + num_user_songs = len(songs) + + for x in range(next_index, upper_lim): + + tmp = songs[x]._asdict() + if tmp['musicId'] in rival_music_list: + rival_music_list[tmp['musicId']].append([{"level": tmp['level'], 'achievement': tmp['achievement'], 'deluxscoreMax': tmp['deluxscoreMax']}]) + + else: + if len(rival_music_list) >= max_ct: + break + rival_music_list[tmp['musicId']] = [{"level": tmp['level'], 'achievement': tmp['achievement'], 'deluxscoreMax': tmp['deluxscoreMax']}] + + next_index = 0 if len(rival_music_list) < max_ct or num_user_songs == upper_lim else upper_lim + self.logger.info(f"Send rival {rival_id} songs {next_index}-{upper_lim} ({len(rival_music_list)}) out of {num_user_songs} for user {user_id} (next idx {next_index})") + + return { + "userId": user_id, + "rivalId": rival_id, + "nextIndex": next_index, + "userRivalMusicList": [{"musicId": x, "userRivalMusicDetailList": y} for x, y in rival_music_list.items()] + } + # CardMaker support added in Universe async def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict: p = await self.data.profile.get_profile_detail(data["userId"], self.version) diff --git a/titles/mai2/schema/score.py b/titles/mai2/schema/score.py index d13faae..59b61b6 100644 --- a/titles/mai2/schema/score.py +++ b/titles/mai2/schema/score.py @@ -319,16 +319,16 @@ class Mai2ScoreData(BaseData): sql = best_score.select( and_( best_score.c.user == user_id, - (best_score.c.song_id == song_id) if song_id is not None else True, + (best_score.c.musicId == song_id) if song_id is not None else True, ) - ) + ).order_by(best_score.c.musicId).order_by(best_score.c.level) else: sql = best_score_old.select( and_( best_score_old.c.user == user_id, - (best_score_old.c.song_id == song_id) if song_id is not None else True, + (best_score_old.c.musicId == song_id) if song_id is not None else True, ) - ) + ).order_by(best_score.c.musicId).order_by(best_score.c.level) result = await self.execute(sql) if result is None: From 8b03f1a4f13487779f4b6036a5ca02e950ddbf5d Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sat, 8 Jun 2024 22:02:31 -0400 Subject: [PATCH 21/76] mai2: fix rival data load failing due to inheritance --- titles/mai2/base.py | 275 ----------------------------------------- titles/mai2/dx.py | 292 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 269 insertions(+), 298 deletions(-) diff --git a/titles/mai2/base.py b/titles/mai2/base.py index 974c94c..4d31213 100644 --- a/titles/mai2/base.py +++ b/titles/mai2/base.py @@ -929,278 +929,3 @@ class Mai2Base: userRecommendSelectionMusicIdList: list[int] """ return {"userId": data["userId"], "userRecommendSelectionMusicIdList": []} - - async def handle_get_user_new_item_api_request(self, data: Dict) -> Dict: - # TODO: Added in 1.41, implement this? - user_id = data["userId"] - version = data.get("version", 1041000) - user_playlog_list = data.get("userPlaylogList", []) - - return { - "userId": user_id, - "itemKind": -1, - "itemId": -1, - } - - async def handle_get_user_rival_data_api_request(self, data: Dict) -> Dict: - user_id = data.get("userId", 0) - rival_id = data.get("rivalId", 0) - - if not user_id or not rival_id: return {} - - rival_pf = await self.data.profile.get_profile_detail(rival_id) - if not rival_pf: return {} - - return { - "userId": user_id, - "userRivalData": { - "rivalId": rival_id, - "rivalName": rival_pf['userName'] - } - } - - async def handle_get_user_rival_music_api_request(self, data: Dict) -> Dict: - user_id = data.get("userId", 0) - rival_id = data.get("rivalId", 0) - next_index = data.get("nextIndex", 0) - max_ct = 100 - upper_lim = next_index + max_ct - rival_music_list: Dict[int, List] = {} - - songs = await self.data.score.get_best_scores(rival_id) - if songs is None: - self.logger.debug("handle_get_user_rival_music_api_request: get_best_scores returned None!") - return { - "userId": user_id, - "rivalId": rival_id, - "nextIndex": 0, - "userRivalMusicList": [] # musicId userRivalMusicDetailList -> level achievement deluxscoreMax - } - - num_user_songs = len(songs) - - for x in range(next_index, upper_lim): - - tmp = songs[x]._asdict() - if tmp['musicId'] in rival_music_list: - rival_music_list[tmp['musicId']].append([{"level": tmp['level'], 'achievement': tmp['achievement'], 'deluxscoreMax': tmp['deluxscoreMax']}]) - - else: - if len(rival_music_list) >= max_ct: - break - rival_music_list[tmp['musicId']] = [{"level": tmp['level'], 'achievement': tmp['achievement'], 'deluxscoreMax': tmp['deluxscoreMax']}] - - next_index = 0 if len(rival_music_list) < max_ct or num_user_songs == upper_lim else upper_lim - self.logger.info(f"Send rival {rival_id} songs {next_index}-{upper_lim} ({len(rival_music_list)}) out of {num_user_songs} for user {user_id} (next idx {next_index})") - - return { - "userId": user_id, - "rivalId": rival_id, - "nextIndex": next_index, - "userRivalMusicList": [{"musicId": x, "userRivalMusicDetailList": y} for x, y in rival_music_list.items()] - } - - # CardMaker support added in Universe - async def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict: - p = await self.data.profile.get_profile_detail(data["userId"], self.version) - if p is None: - return {} - - return { - "userName": p["userName"], - "rating": p["playerRating"], - # hardcode lastDataVersion for CardMaker - "lastDataVersion": "1.20.00", # Future versiohs should replace this with the correct version - # checks if the user is still logged in - "isLogin": False, - "isExistSellingCard": True, - } - - async def handle_cm_get_user_data_api_request(self, data: Dict) -> Dict: - # user already exists, because the preview checks that already - p = await self.data.profile.get_profile_detail(data["userId"], self.version) - - cards = await 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") - - return {"userId": data["userId"], "userData": user_data} - - async def handle_cm_login_api_request(self, data: Dict) -> Dict: - return {"returnCode": 1} - - async def handle_cm_logout_api_request(self, data: Dict) -> Dict: - return {"returnCode": 1} - - async def handle_cm_get_selling_card_api_request(self, data: Dict) -> Dict: - selling_cards = await self.data.static.get_enabled_cards(self.version) - if selling_cards is None: - return {"length": 0, "sellingCardList": []} - - selling_card_list = [] - for card in selling_cards: - tmp = card._asdict() - tmp.pop("id") - tmp.pop("version") - tmp.pop("cardName") - tmp.pop("enabled") - - tmp["startDate"] = datetime.strftime( - tmp["startDate"], Mai2Constants.DATE_TIME_FORMAT - ) - tmp["endDate"] = datetime.strftime( - tmp["endDate"], Mai2Constants.DATE_TIME_FORMAT - ) - tmp["noticeStartDate"] = datetime.strftime( - tmp["noticeStartDate"], Mai2Constants.DATE_TIME_FORMAT - ) - tmp["noticeEndDate"] = datetime.strftime( - tmp["noticeEndDate"], Mai2Constants.DATE_TIME_FORMAT - ) - - selling_card_list.append(tmp) - - return {"length": len(selling_card_list), "sellingCardList": selling_card_list} - - async def handle_cm_get_user_card_api_request(self, data: Dict) -> Dict: - user_cards = await self.data.item.get_cards(data["userId"]) - if user_cards is None: - return {"returnCode": 1, "length": 0, "nextIndex": 0, "userCardList": []} - - max_ct = data["maxCount"] - next_idx = data["nextIndex"] - start_idx = next_idx - end_idx = max_ct + start_idx - - if len(user_cards[start_idx:]) > max_ct: - next_idx += max_ct - else: - next_idx = 0 - - card_list = [] - for card in user_cards: - tmp = card._asdict() - tmp.pop("id") - tmp.pop("user") - - tmp["startDate"] = datetime.strftime( - tmp["startDate"], Mai2Constants.DATE_TIME_FORMAT - ) - tmp["endDate"] = datetime.strftime( - tmp["endDate"], Mai2Constants.DATE_TIME_FORMAT - ) - card_list.append(tmp) - - return { - "returnCode": 1, - "length": len(card_list[start_idx:end_idx]), - "nextIndex": next_idx, - "userCardList": card_list[start_idx:end_idx], - } - - async def handle_cm_get_user_item_api_request(self, data: Dict) -> Dict: - await self.handle_get_user_item_api_request(data) - - async def handle_cm_get_user_character_api_request(self, data: Dict) -> Dict: - characters = await self.data.item.get_characters(data["userId"]) - - chara_list = [] - for chara in characters: - chara_list.append( - { - "characterId": chara["characterId"], - # no clue why those values are even needed - "point": 0, - "count": 0, - "level": chara["level"], - "nextAwake": 0, - "nextAwakePercent": 0, - "favorite": False, - "awakening": chara["awakening"], - "useCount": chara["useCount"], - } - ) - - return { - "returnCode": 1, - "length": len(chara_list), - "userCharacterList": chara_list, - } - - async def handle_cm_get_user_card_print_error_api_request(self, data: Dict) -> Dict: - return {"length": 0, "userPrintDetailList": []} - - async def handle_cm_upsert_user_print_api_request(self, data: Dict) -> Dict: - user_id = data["userId"] - upsert = data["userPrintDetail"] - - # set a random card serial number - serial_id = "".join([str(randint(0, 9)) for _ in range(20)]) - - # calculate start and end date of the card - start_date = datetime.utcnow() - end_date = datetime.utcnow() + timedelta(days=15) - - user_card = upsert["userCard"] - await self.data.item.put_card( - user_id, - user_card["cardId"], - user_card["cardTypeId"], - user_card["charaId"], - user_card["mapId"], - # add the correct start date and also the end date in 15 days - start_date, - end_date, - ) - - # get the profile extend to save the new bought card - extend = await self.data.profile.get_profile_extend(user_id, self.version) - if extend: - extend = extend._asdict() - # parse the selectedCardList - # 6 = Freedom Pass, 4 = Gold Pass (cardTypeId) - selected_cards: List = extend["selectedCardList"] - - # if no pass is already added, add the corresponding pass - if not user_card["cardTypeId"] in selected_cards: - selected_cards.insert(0, user_card["cardTypeId"]) - - extend["selectedCardList"] = selected_cards - await self.data.profile.put_profile_extend(user_id, self.version, extend) - - # properly format userPrintDetail for the database - upsert.pop("userCard") - upsert.pop("serialId") - upsert["printDate"] = datetime.strptime(upsert["printDate"], "%Y-%m-%d") - - await self.data.item.put_user_print_detail(user_id, serial_id, upsert) - - return { - "returnCode": 1, - "orderId": 0, - "serialId": serial_id, - "startDate": datetime.strftime(start_date, Mai2Constants.DATE_TIME_FORMAT), - "endDate": datetime.strftime(end_date, Mai2Constants.DATE_TIME_FORMAT), - } - - async def handle_cm_upsert_user_printlog_api_request(self, data: Dict) -> Dict: - return { - "returnCode": 1, - "orderId": 0, - "serialId": data["userPrintlog"]["serialId"], - } - - async def handle_cm_upsert_buy_card_api_request(self, data: Dict) -> Dict: - return {"returnCode": 1} diff --git a/titles/mai2/dx.py b/titles/mai2/dx.py index 4423824..2c7d960 100644 --- a/titles/mai2/dx.py +++ b/titles/mai2/dx.py @@ -563,33 +563,74 @@ class Mai2DX(Mai2Base): return {"userId": data["userId"], "length": 0, "userRegionList": []} async def handle_get_user_rival_data_api_request(self, data: Dict) -> Dict: - user_id = data["userId"] - rival_id = data["rivalId"] + user_id = data.get("userId", 0) + rival_id = data.get("rivalId", 0) - """ - class UserRivalData: - rivalId: int - rivalName: str - """ - return {"userId": user_id, "userRivalData": {}} + if not user_id or not rival_id: return {} + + rival_pf = await self.data.profile.get_profile_detail(rival_id) + if not rival_pf: return {} + + return { + "userId": user_id, + "userRivalData": { + "rivalId": rival_id, + "rivalName": rival_pf['userName'] + } + } async def handle_get_user_rival_music_api_request(self, data: Dict) -> Dict: + user_id = data.get("userId", 0) + rival_id = data.get("rivalId", 0) + next_index = data.get("nextIndex", 0) + max_ct = 100 + upper_lim = next_index + max_ct + rival_music_list: Dict[int, List] = {} + + songs = await self.data.score.get_best_scores(rival_id) + if songs is None: + self.logger.debug("handle_get_user_rival_music_api_request: get_best_scores returned None!") + return { + "userId": user_id, + "rivalId": rival_id, + "nextIndex": 0, + "userRivalMusicList": [] # musicId userRivalMusicDetailList -> level achievement deluxscoreMax + } + + num_user_songs = len(songs) + + for x in range(next_index, upper_lim): + + tmp = songs[x]._asdict() + if tmp['musicId'] in rival_music_list: + rival_music_list[tmp['musicId']].append([{"level": tmp['level'], 'achievement': tmp['achievement'], 'deluxscoreMax': tmp['deluxscoreMax']}]) + + else: + if len(rival_music_list) >= max_ct: + break + rival_music_list[tmp['musicId']] = [{"level": tmp['level'], 'achievement': tmp['achievement'], 'deluxscoreMax': tmp['deluxscoreMax']}] + + next_index = 0 if len(rival_music_list) < max_ct or num_user_songs == upper_lim else upper_lim + self.logger.info(f"Send rival {rival_id} songs {next_index}-{upper_lim} ({len(rival_music_list)}) out of {num_user_songs} for user {user_id} (next idx {next_index})") + + return { + "userId": user_id, + "rivalId": rival_id, + "nextIndex": next_index, + "userRivalMusicList": [{"musicId": x, "userRivalMusicDetailList": y} for x, y in rival_music_list.items()] + } + + async def handle_get_user_new_item_api_request(self, data: Dict) -> Dict: + # TODO: Added in 1.41, implement this? user_id = data["userId"] - rival_id = data["rivalId"] - next_idx = data["nextIndex"] - rival_music_levels = data["userRivalMusicLevelList"] - - """ - class UserRivalMusicList: - class UserRivalMusicDetailList: - level: int - achievement: int - deluxscoreMax: int - - musicId: int - userRivalMusicDetailList: list[UserRivalMusicDetailList] - """ - return {"userId": user_id, "nextIndex": 0, "userRivalMusicList": []} + version = data.get("version", 1041000) + user_playlog_list = data.get("userPlaylogList", []) + + return { + "userId": user_id, + "itemKind": -1, + "itemId": -1, + } async def handle_get_user_music_api_request(self, data: Dict) -> Dict: user_id = data.get("userId", 0) @@ -636,3 +677,208 @@ class Mai2DX(Mai2Base): return ret ret['loginId'] = ret.get('loginCount', 0) return ret + + # CardMaker support added in Universe + async def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict: + p = await self.data.profile.get_profile_detail(data["userId"], self.version) + if p is None: + return {} + + return { + "userName": p["userName"], + "rating": p["playerRating"], + # hardcode lastDataVersion for CardMaker + "lastDataVersion": "1.20.00", # Future versiohs should replace this with the correct version + # checks if the user is still logged in + "isLogin": False, + "isExistSellingCard": True, + } + + async def handle_cm_get_user_data_api_request(self, data: Dict) -> Dict: + # user already exists, because the preview checks that already + p = await self.data.profile.get_profile_detail(data["userId"], self.version) + + cards = await 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") + + return {"userId": data["userId"], "userData": user_data} + + async def handle_cm_login_api_request(self, data: Dict) -> Dict: + return {"returnCode": 1} + + async def handle_cm_logout_api_request(self, data: Dict) -> Dict: + return {"returnCode": 1} + + async def handle_cm_get_selling_card_api_request(self, data: Dict) -> Dict: + selling_cards = await self.data.static.get_enabled_cards(self.version) + if selling_cards is None: + return {"length": 0, "sellingCardList": []} + + selling_card_list = [] + for card in selling_cards: + tmp = card._asdict() + tmp.pop("id") + tmp.pop("version") + tmp.pop("cardName") + tmp.pop("enabled") + + tmp["startDate"] = datetime.strftime( + tmp["startDate"], Mai2Constants.DATE_TIME_FORMAT + ) + tmp["endDate"] = datetime.strftime( + tmp["endDate"], Mai2Constants.DATE_TIME_FORMAT + ) + tmp["noticeStartDate"] = datetime.strftime( + tmp["noticeStartDate"], Mai2Constants.DATE_TIME_FORMAT + ) + tmp["noticeEndDate"] = datetime.strftime( + tmp["noticeEndDate"], Mai2Constants.DATE_TIME_FORMAT + ) + + selling_card_list.append(tmp) + + return {"length": len(selling_card_list), "sellingCardList": selling_card_list} + + async def handle_cm_get_user_card_api_request(self, data: Dict) -> Dict: + user_cards = await self.data.item.get_cards(data["userId"]) + if user_cards is None: + return {"returnCode": 1, "length": 0, "nextIndex": 0, "userCardList": []} + + max_ct = data["maxCount"] + next_idx = data["nextIndex"] + start_idx = next_idx + end_idx = max_ct + start_idx + + if len(user_cards[start_idx:]) > max_ct: + next_idx += max_ct + else: + next_idx = 0 + + card_list = [] + for card in user_cards: + tmp = card._asdict() + tmp.pop("id") + tmp.pop("user") + + tmp["startDate"] = datetime.strftime( + tmp["startDate"], Mai2Constants.DATE_TIME_FORMAT + ) + tmp["endDate"] = datetime.strftime( + tmp["endDate"], Mai2Constants.DATE_TIME_FORMAT + ) + card_list.append(tmp) + + return { + "returnCode": 1, + "length": len(card_list[start_idx:end_idx]), + "nextIndex": next_idx, + "userCardList": card_list[start_idx:end_idx], + } + + async def handle_cm_get_user_item_api_request(self, data: Dict) -> Dict: + await self.handle_get_user_item_api_request(data) + + async def handle_cm_get_user_character_api_request(self, data: Dict) -> Dict: + characters = await self.data.item.get_characters(data["userId"]) + + chara_list = [] + for chara in characters: + chara_list.append( + { + "characterId": chara["characterId"], + # no clue why those values are even needed + "point": 0, + "count": 0, + "level": chara["level"], + "nextAwake": 0, + "nextAwakePercent": 0, + "favorite": False, + "awakening": chara["awakening"], + "useCount": chara["useCount"], + } + ) + + return { + "returnCode": 1, + "length": len(chara_list), + "userCharacterList": chara_list, + } + + async def handle_cm_get_user_card_print_error_api_request(self, data: Dict) -> Dict: + return {"length": 0, "userPrintDetailList": []} + + async def handle_cm_upsert_user_print_api_request(self, data: Dict) -> Dict: + user_id = data["userId"] + upsert = data["userPrintDetail"] + + # set a random card serial number + serial_id = "".join([str(randint(0, 9)) for _ in range(20)]) + + # calculate start and end date of the card + start_date = datetime.utcnow() + end_date = datetime.utcnow() + timedelta(days=15) + + user_card = upsert["userCard"] + await self.data.item.put_card( + user_id, + user_card["cardId"], + user_card["cardTypeId"], + user_card["charaId"], + user_card["mapId"], + # add the correct start date and also the end date in 15 days + start_date, + end_date, + ) + + # get the profile extend to save the new bought card + extend = await self.data.profile.get_profile_extend(user_id, self.version) + if extend: + extend = extend._asdict() + # parse the selectedCardList + # 6 = Freedom Pass, 4 = Gold Pass (cardTypeId) + selected_cards: List = extend["selectedCardList"] + + # if no pass is already added, add the corresponding pass + if not user_card["cardTypeId"] in selected_cards: + selected_cards.insert(0, user_card["cardTypeId"]) + + extend["selectedCardList"] = selected_cards + await self.data.profile.put_profile_extend(user_id, self.version, extend) + + # properly format userPrintDetail for the database + upsert.pop("userCard") + upsert.pop("serialId") + upsert["printDate"] = datetime.strptime(upsert["printDate"], "%Y-%m-%d") + + await self.data.item.put_user_print_detail(user_id, serial_id, upsert) + + return { + "returnCode": 1, + "orderId": 0, + "serialId": serial_id, + "startDate": datetime.strftime(start_date, Mai2Constants.DATE_TIME_FORMAT), + "endDate": datetime.strftime(end_date, Mai2Constants.DATE_TIME_FORMAT), + } + + async def handle_cm_upsert_user_printlog_api_request(self, data: Dict) -> Dict: + return { + "returnCode": 1, + "orderId": 0, + "serialId": data["userPrintlog"]["serialId"], + } + + async def handle_cm_upsert_buy_card_api_request(self, data: Dict) -> Dict: + return {"returnCode": 1} From 319aea098f1dbe587f4aea15997cd1e071bc948c Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sat, 8 Jun 2024 22:19:59 -0400 Subject: [PATCH 22/76] dx: fix GetUserRivalMusicApi list index out of range --- titles/mai2/dx.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/titles/mai2/dx.py b/titles/mai2/dx.py index 2c7d960..3c5c319 100644 --- a/titles/mai2/dx.py +++ b/titles/mai2/dx.py @@ -600,6 +600,8 @@ class Mai2DX(Mai2Base): num_user_songs = len(songs) for x in range(next_index, upper_lim): + if x >= num_user_songs: + break tmp = songs[x]._asdict() if tmp['musicId'] in rival_music_list: From e7ddfcda2e25ca30868908bf55426958eadd6ea6 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sat, 8 Jun 2024 22:29:49 -0400 Subject: [PATCH 23/76] mai2: oops, forgot version --- titles/mai2/dx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/titles/mai2/dx.py b/titles/mai2/dx.py index 3c5c319..04c9172 100644 --- a/titles/mai2/dx.py +++ b/titles/mai2/dx.py @@ -568,7 +568,7 @@ class Mai2DX(Mai2Base): if not user_id or not rival_id: return {} - rival_pf = await self.data.profile.get_profile_detail(rival_id) + rival_pf = await self.data.profile.get_profile_detail(rival_id, self.version) if not rival_pf: return {} return { From b4b8650acc846cff59092f9e95218ab50d162549 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sun, 9 Jun 2024 03:05:57 -0400 Subject: [PATCH 24/76] mai2: add basic webui --- core/frontend.py | 16 +- titles/mai2/__init__.py | 2 + titles/mai2/frontend.py | 190 +++++++++++++++++++ titles/mai2/schema/profile.py | 16 ++ titles/mai2/schema/score.py | 20 ++ titles/mai2/templates/css/mai2_style.css | 195 ++++++++++++++++++++ titles/mai2/templates/mai2_header.jinja | 17 ++ titles/mai2/templates/mai2_index.jinja | 134 ++++++++++++++ titles/mai2/templates/mai2_playlog.jinja | 225 +++++++++++++++++++++++ 9 files changed, 813 insertions(+), 2 deletions(-) create mode 100644 titles/mai2/frontend.py create mode 100644 titles/mai2/templates/css/mai2_style.css create mode 100644 titles/mai2/templates/mai2_header.jinja create mode 100644 titles/mai2/templates/mai2_index.jinja create mode 100644 titles/mai2/templates/mai2_playlog.jinja diff --git a/core/frontend.py b/core/frontend.py index b9d1604..31a6254 100644 --- a/core/frontend.py +++ b/core/frontend.py @@ -44,12 +44,13 @@ class ShopOwner(): self.permissions = perms class UserSession(): - def __init__(self, usr_id: int = 0, ip: str = "", perms: int = 0, ongeki_ver: int = 7, chunithm_ver: int = -1): + def __init__(self, usr_id: int = 0, ip: str = "", perms: int = 0, ongeki_ver: int = 7, chunithm_ver: int = -1, maimai_version: int = -1): self.user_id = usr_id self.current_ip = ip self.permissions = perms self.ongeki_version = ongeki_ver self.chunithm_version = chunithm_ver + self.maimai_version = maimai_version class FrontendServlet(): def __init__(self, cfg: CoreConfig, config_dir: str) -> None: @@ -216,6 +217,7 @@ class FE_Base(): sesh.current_ip = tk['current_ip'] sesh.permissions = tk['permissions'] sesh.chunithm_version = tk['chunithm_version'] + sesh.maimai_version = tk['maimai_version'] if sesh.user_id <= 0: self.logger.error("User session failed to validate due to an invalid ID!") @@ -260,7 +262,17 @@ class FE_Base(): def encode_session(self, sesh: UserSession, exp_seconds: int = 86400) -> str: try: - return jwt.encode({ "user_id": sesh.user_id, "current_ip": sesh.current_ip, "permissions": sesh.permissions, "ongeki_version": sesh.ongeki_version, "chunithm_version": sesh.chunithm_version, "exp": int(datetime.now(tz=timezone.utc).timestamp()) + exp_seconds }, b64decode(self.core_config.frontend.secret), algorithm="HS256") + return jwt.encode({ + "user_id": sesh.user_id, + "current_ip": sesh.current_ip, + "permissions": sesh.permissions, + "ongeki_version": sesh.ongeki_version, + "chunithm_version": sesh.chunithm_version, + "maimai_version": sesh.maimai_version, + "exp": int(datetime.now(tz=timezone.utc).timestamp()) + exp_seconds }, + b64decode(self.core_config.frontend.secret), + algorithm="HS256" + ) except jwt.InvalidKeyError: self.logger.error("Failed to encode User session because the secret is invalid!") return "" diff --git a/titles/mai2/__init__.py b/titles/mai2/__init__.py index 74cfddf..234e864 100644 --- a/titles/mai2/__init__.py +++ b/titles/mai2/__init__.py @@ -2,10 +2,12 @@ from titles.mai2.index import Mai2Servlet from titles.mai2.const import Mai2Constants from titles.mai2.database import Mai2Data from titles.mai2.read import Mai2Reader +from .frontend import Mai2Frontend index = Mai2Servlet database = Mai2Data reader = Mai2Reader +frontend = Mai2Frontend game_codes = [ Mai2Constants.GAME_CODE_DX, Mai2Constants.GAME_CODE_FINALE, diff --git a/titles/mai2/frontend.py b/titles/mai2/frontend.py new file mode 100644 index 0000000..6ce8828 --- /dev/null +++ b/titles/mai2/frontend.py @@ -0,0 +1,190 @@ +from typing import List +from starlette.routing import Route, Mount +from starlette.requests import Request +from starlette.responses import Response, RedirectResponse +from os import path +import yaml +import jinja2 + +from core.frontend import FE_Base, UserSession +from core.config import CoreConfig +from .database import Mai2Data +from .config import Mai2Config +from .const import Mai2Constants + +class Mai2Frontend(FE_Base): + def __init__( + self, cfg: CoreConfig, environment: jinja2.Environment, cfg_dir: str + ) -> None: + super().__init__(cfg, environment) + self.data = Mai2Data(cfg) + self.game_cfg = Mai2Config() + if path.exists(f"{cfg_dir}/{Mai2Constants.CONFIG_NAME}"): + self.game_cfg.update( + yaml.safe_load(open(f"{cfg_dir}/{Mai2Constants.CONFIG_NAME}")) + ) + self.nav_name = "maimai" + + def get_routes(self) -> List[Route]: + return [ + Route("/", self.render_GET, methods=['GET']), + Mount("/playlog", routes=[ + Route("/", self.render_GET_playlog, methods=['GET']), + Route("/{index}", self.render_GET_playlog, methods=['GET']), + ]), + Route("/update.name", self.update_name, methods=['POST']), + Route("/version.change", self.version_change, methods=['POST']), + ] + + async def render_GET(self, request: Request) -> bytes: + template = self.environment.get_template( + "titles/mai2/templates/mai2_index.jinja" + ) + usr_sesh = self.validate_session(request) + if not usr_sesh: + usr_sesh = UserSession() + + if usr_sesh.user_id > 0: + versions = await self.data.profile.get_all_profile_versions(usr_sesh.user_id) + profile = [] + if versions: + # maimai_version is -1 means it is not initialized yet, select a default version from existing. + if usr_sesh.maimai_version < 0: + usr_sesh.maimai_version = versions[0]['version'] + profile = await self.data.profile.get_profile_detail(usr_sesh.user_id, usr_sesh.maimai_version) + versions = [x['version'] for x in versions] + + resp = Response(template.render( + title=f"{self.core_config.server.name} | {self.nav_name}", + game_list=self.environment.globals["game_list"], + sesh=vars(usr_sesh), + user_id=usr_sesh.user_id, + profile=profile, + version_list=Mai2Constants.VERSION_STRING, + versions=versions, + cur_version=usr_sesh.maimai_version + ), media_type="text/html; charset=utf-8") + + if usr_sesh.maimai_version >= 0: + encoded_sesh = self.encode_session(usr_sesh) + resp.delete_cookie("DIANA_SESH") + resp.set_cookie("DIANA_SESH", encoded_sesh) + return resp + + else: + return RedirectResponse("/gate/", 303) + + async def render_GET_playlog(self, request: Request) -> bytes: + template = self.environment.get_template( + "titles/mai2/templates/mai2_playlog.jinja" + ) + usr_sesh = self.validate_session(request) + if not usr_sesh: + print("wtf") + usr_sesh = UserSession() + + if usr_sesh.user_id > 0: + if usr_sesh.maimai_version < 0: + print(usr_sesh.maimai_version) + return RedirectResponse("/game/mai2/", 303) + path_index = request.path_params.get('index') + if not path_index or int(path_index) < 1: + index = 0 + else: + index = int(path_index) - 1 # 0 and 1 are 1st page + user_id = usr_sesh.user_id + playlog_count = await self.data.score.get_user_playlogs_count(user_id) + if playlog_count < index * 20 : + return Response(template.render( + title=f"{self.core_config.server.name} | {self.nav_name}", + game_list=self.environment.globals["game_list"], + sesh=vars(usr_sesh), + playlog_count=0 + ), media_type="text/html; charset=utf-8") + playlog = await self.data.score.get_playlogs(user_id, index, 20) + playlog_with_title = [] + for record in playlog: + music_chart = await self.data.static.get_music_chart(usr_sesh.maimai_version, record.musicId, record.level) + if music_chart: + difficultyNum=music_chart.chartId + difficulty=music_chart.difficulty + artist=music_chart.artist + title=music_chart.title + else: + difficultyNum=0 + difficulty=0 + artist="unknown" + title="musicid: " + str(record.musicId) + playlog_with_title.append({ + "raw": record, + "title": title, + "difficultyNum": difficultyNum, + "difficulty": difficulty, + "artist": artist, + }) + return Response(template.render( + title=f"{self.core_config.server.name} | {self.nav_name}", + game_list=self.environment.globals["game_list"], + sesh=vars(usr_sesh), + user_id=usr_sesh.user_id, + playlog=playlog_with_title, + playlog_count=playlog_count + ), media_type="text/html; charset=utf-8") + else: + return RedirectResponse("/gate/", 303) + + async def update_name(self, request: Request) -> bytes: + usr_sesh = self.validate_session(request) + if not usr_sesh: + return RedirectResponse("/gate/", 303) + + form_data = await request.form() + new_name: str = form_data.get("new_name") + new_name_full = "" + + if not new_name: + return RedirectResponse("/gate/?e=4", 303) + + if len(new_name) > 8: + return RedirectResponse("/gate/?e=8", 303) + + for x in new_name: # FIXME: This will let some invalid characters through atm + o = ord(x) + try: + if o == 0x20: + new_name_full += chr(0x3000) + elif o < 0x7F and o > 0x20: + new_name_full += chr(o + 0xFEE0) + elif o <= 0x7F: + self.logger.warn(f"Invalid ascii character {o:02X}") + return RedirectResponse("/gate/?e=4", 303) + else: + new_name_full += x + + except Exception as e: + self.logger.error(f"Something went wrong parsing character {o:04X} - {e}") + return RedirectResponse("/gate/?e=4", 303) + + if not await self.data.profile.update_name(usr_sesh.user_id, new_name_full): + return RedirectResponse("/gate/?e=999", 303) + + return RedirectResponse("/game/mai2/?s=1", 303) + + async def version_change(self, request: Request): + usr_sesh = self.validate_session(request) + if not usr_sesh: + usr_sesh = UserSession() + + if usr_sesh.user_id > 0: + form_data = await request.form() + maimai_version = form_data.get("version") + self.logger.info(f"version change to: {maimai_version}") + if(maimai_version.isdigit()): + usr_sesh.maimai_version=int(maimai_version) + encoded_sesh = self.encode_session(usr_sesh) + self.logger.info(f"Created session with JWT {encoded_sesh}") + resp = RedirectResponse("/game/mai2/", 303) + resp.set_cookie("DIANA_SESH", encoded_sesh) + return resp + else: + return RedirectResponse("/gate/", 303) \ No newline at end of file diff --git a/titles/mai2/schema/profile.py b/titles/mai2/schema/profile.py index d45ee16..1b76b07 100644 --- a/titles/mai2/schema/profile.py +++ b/titles/mai2/schema/profile.py @@ -511,6 +511,11 @@ rival = Table( ) class Mai2ProfileData(BaseData): + async def get_all_profile_versions(self, user_id: int) -> Optional[List[Row]]: + result = await self.execute(detail.select(detail.c.user == user_id)) + if result: + return result.fetchall() + async def put_profile_detail( self, user_id: int, version: int, detail_data: Dict, is_dx: bool = True ) -> Optional[Row]: @@ -899,3 +904,14 @@ class Mai2ProfileData(BaseData): result = await self.execute(rival.delete(and_(rival.c.user == user_id, rival.c.rival == rival_id))) if not result: self.logger.error(f"Failed to remove rival {rival_id} for user {user_id}!") + + async def update_name(self, user_id: int, new_name: str) -> bool: + sql = detail.update(detail.c.user == user_id).values( + userName=new_name + ) + result = await self.execute(sql) + + if result is None: + self.logger.warning(f"Failed to set user {user_id} name to {new_name}") + return False + return True diff --git a/titles/mai2/schema/score.py b/titles/mai2/schema/score.py index 59b61b6..d4ea5b9 100644 --- a/titles/mai2/schema/score.py +++ b/titles/mai2/schema/score.py @@ -398,3 +398,23 @@ class Mai2ScoreData(BaseData): if result is None: return None return result.fetchall() + + async def get_playlogs(self, user_id: int, idx: int = 0, limit: int = 0) -> Optional[List[Row]]: + sql = playlog.select(playlog.c.user == user_id) + + if limit: + sql = sql.limit(limit) + if idx: + sql = sql.offset(idx * limit) + + result = await self.execute(sql) + if result: + return result.fetchall() + + async def get_user_playlogs_count(self, aime_id: int) -> Optional[Row]: + sql = select(func.count()).where(playlog.c.user == aime_id) + result = await self.execute(sql) + if result is None: + self.logger.warning(f"aime_id {aime_id} has no playlog ") + return None + return result.scalar() diff --git a/titles/mai2/templates/css/mai2_style.css b/titles/mai2/templates/css/mai2_style.css new file mode 100644 index 0000000..4aceab8 --- /dev/null +++ b/titles/mai2/templates/css/mai2_style.css @@ -0,0 +1,195 @@ +.mai2-header { + text-align: center; +} + +ul.mai2-navi { + list-style-type: none; + padding: 0; + overflow: hidden; + background-color: #333; + text-align: center; + display: inline-block; +} + +ul.mai2-navi li { + display: inline-block; +} + +ul.mai2-navi li a { + display: block; + color: white; + text-align: center; + padding: 14px 16px; + text-decoration: none; +} + +ul.mai2-navi li a:hover:not(.active) { + background-color: #111; +} + +ul.mai2-navi li a.active { + background-color: #4CAF50; +} + +ul.mai2-navi li.right { + float: right; +} + +@media screen and (max-width: 600px) { + + ul.mai2-navi li.right, + ul.mai2-navi li { + float: none; + display: block; + text-align: center; + } +} + +table { + border-collapse: collapse; + border-spacing: 0; + border-collapse: separate; + overflow: hidden; + background-color: #555555; + +} + +th, td { + text-align: left; + border: none; + +} + +th { + color: white; +} + +.table-rowdistinct tr:nth-child(even) { + background-color: #303030; +} + +.table-rowdistinct tr:nth-child(odd) { + background-color: #555555; +} + +caption { + text-align: center; + color: white; + font-size: 18px; + font-weight: bold; +} + +.table-large { + margin: 16px; +} + +.table-large th, +.table-large td { + padding: 8px; +} + +.table-small { + width: 100%; + margin: 4px; +} + +.table-small th, +.table-small td { + padding: 2px; +} + +.bg-card { + background-color: #555555; +} + +.card-hover { + transition: all 0.2s ease-in-out; +} + +.card-hover:hover { + transform: scale(1.02); +} + +.basic { + color: #28a745; + font-weight: bold; +} + +.hard { + color: #ffc107; + + font-weight: bold; +} + +.expert { + color: #dc3545; + font-weight: bold; +} + +.master { + color: #dd09e8; + font-weight: bold; +} + +.ultimate { + color: #000000; + font-weight: bold; +} + +.score { + color: #ffffff; + font-weight: bold; +} + +.rainbow { + background: linear-gradient(to right, red, yellow, lime, aqua, blue, fuchsia) 0 / 5em; + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + font-weight: bold; +} + +.platinum { + color: #FFFF00; + font-weight: bold; +} + +.gold { + color: #FFFF00; + font-weight: bold; +} + +.scrolling-text { + overflow: hidden; +} + +.scrolling-text p { + white-space: nowrap; + display: inline-block; + +} + +.scrolling-text h6 { + white-space: nowrap; + display: inline-block; + +} + +.scrolling-text h5 { + white-space: nowrap; + display: inline-block; + +} + +.scrolling { + animation: scroll 10s linear infinite; +} + +@keyframes scroll { + 0% { + transform: translateX(100%); + } + 100% { + transform: translateX(-100%); + } +} \ No newline at end of file diff --git a/titles/mai2/templates/mai2_header.jinja b/titles/mai2/templates/mai2_header.jinja new file mode 100644 index 0000000..4a4cb86 --- /dev/null +++ b/titles/mai2/templates/mai2_header.jinja @@ -0,0 +1,17 @@ +
+

maimai

+ +
+ \ No newline at end of file diff --git a/titles/mai2/templates/mai2_index.jinja b/titles/mai2/templates/mai2_index.jinja new file mode 100644 index 0000000..6490fdc --- /dev/null +++ b/titles/mai2/templates/mai2_index.jinja @@ -0,0 +1,134 @@ +{% extends "core/templates/index.jinja" %} +{% block content %} + +
+ {% include 'titles/mai2/templates/mai2_header.jinja' %} + {% if profile is defined and profile is not none and profile|length > 0 %} +
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
OVERVIEW
{{ profile.userName }} + +
version: + + {% if versions | length > 1 %} +

You have {{ versions | length }} versions.

+ {% endif %} +
Rating: + + {{ profile.playerRating }} + + + (highest: {{ profile.highestRating }}) + +
Play Counts:{{ profile.playCount }}
Last Play Date:{{ profile.lastPlayDate }}
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
SCORE
Total Delux Score:{{ profile.totalDeluxscore }}
Total Basic Delux Score:{{ profile.totalBasicDeluxscore }}
Total Advanced Delux Score:{{ profile.totalAdvancedDeluxscore }}
Total Expert Delux Score:{{ profile.totalExpertDeluxscore }}
Total Master Delux Score:{{ profile.totalMasterDeluxscore }}
Total ReMaster Delux Score:{{ profile.totalReMasterDeluxscore }}
+
+
+
+ {% if error is defined %} + {% include "core/templates/widgets/err_banner.jinja" %} + {% endif %} + {% elif sesh is defined and sesh is not none and sesh.user_id > 0 %} + No profile information found for this account. + {% else %} + Login to view profile information. + {% endif %} +
+ + +{% endblock content %} \ No newline at end of file diff --git a/titles/mai2/templates/mai2_playlog.jinja b/titles/mai2/templates/mai2_playlog.jinja new file mode 100644 index 0000000..3e1d5fd --- /dev/null +++ b/titles/mai2/templates/mai2_playlog.jinja @@ -0,0 +1,225 @@ +{% extends "core/templates/index.jinja" %} +{% block content %} + +
+ {% include 'titles/mai2/templates/mai2_header.jinja' %} + {% if playlog is defined and playlog is not none %} +
+

Playlog counts: {{ playlog_count }}

+ {% set rankName = ['D', 'C', 'B', 'BB', 'BBB', 'A', 'AA', 'AAA', 'S', 'S+', 'SS', 'SS+', 'SSS', 'SSS+'] %} + {% set difficultyName = ['basic', 'hard', 'expert', 'master', 'ultimate'] %} + {% for record in playlog %} +
+
+
+
+
{{ record.title }}
+
+
{{ record.artist }}
+
+
+
{{ record.raw.userPlayDate }}
+
TRACK {{ record.raw.trackNo }}
+
+
+
+
+

{{ record.raw.deluxscore }}

+

{{ rankName[record.raw.rank] }}

+
+ {{ difficultyName[record.raw.level] }}  {{ record.difficulty }} +
+
+
+ + + + + + + + + + + + + + + + + + + + + +
CRITICAL PERFECT + Tap: {{ record.raw.tapCriticalPerfect }}
+ Hold: {{ record.raw.holdCriticalPerfect }}
+ Slide: {{ record.raw.slideCriticalPerfect }}
+ Touch: {{ record.raw.touchCriticalPerfect }}
+ Break: {{ record.raw.breakCriticalPerfect }} +
PERFECT + Tap: {{ record.raw.tapPerfect }}
+ Hold: {{ record.raw.holdPerfect }}
+ Slide: {{ record.raw.slidePerfect }}
+ Touch: {{ record.raw.touchPerfect }}
+ Break: {{ record.raw.breakPerfect }} +
GREAT + Tap: {{ record.raw.tapGreat }}
+ Hold: {{ record.raw.holdGreat }}
+ Slide: {{ record.raw.slideGreat }}
+ Touch: {{ record.raw.touchGreat }}
+ Break: {{ record.raw.breakGreat }} +
GOOD + Tap: {{ record.raw.tapGood }}
+ Hold: {{ record.raw.holdGood }}
+ Slide: {{ record.raw.slideGood }}
+ Touch: {{ record.raw.touchGood }}
+ Break: {{ record.raw.breakGood }} +
MISS + Tap: {{ record.raw.tapMiss }}
+ Hold: {{ record.raw.holdMiss }}
+ Slide: {{ record.raw.slideMiss }}
+ Touch: {{ record.raw.touchMiss }}
+ Break: {{ record.raw.breakMiss }} +
+
+
+ {%if record.raw.comboStatus == 1 %} +
FULL COMBO
+ {% endif %} + {%if record.raw.comboStatus == 2 %} +
FULL COMBO +
+ {% endif %} + {%if record.raw.comboStatus == 3 %} +
ALL PERFECT
+ {% endif %} + {%if record.raw.comboStatus == 4 %} +
ALL PERFECT +
+ {% endif %} + {%if record.raw.syncStatus == 1 %} +
FULL SYNC
+ {% endif %} + {%if record.raw.syncStatus == 2 %} +
FULL SYNC +
+ {% endif %} + {%if record.raw.syncStatus == 3 %} +
FULL SYNC DX
+ {% endif %} + {%if record.raw.syncStatus == 4 %} +
FULL SYNC DX +
+ {% endif %} + {%if record.raw.isAchieveNewRecord == 1 or record.raw.isDeluxscoreNewRecord == 1 %} +
NEW RECORD
+ {% endif %} +
+
+
+
+ {% endfor %} +
+ {% set playlog_pages = playlog_count // 20 + 1 %} + {% elif sesh is defined and sesh is not none and sesh.user_id > 0 %} + No Playlog information found for this account. + {% else %} + Login to view profile information. + {% endif %} +
+ + + +{% endblock content %} \ No newline at end of file From e69922d91b06e8ae5046755889999ad7defb9f19 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sun, 9 Jun 2024 03:14:43 -0400 Subject: [PATCH 25/76] ongeki: fix frontend versions --- core/allnet.py | 2 +- core/frontend.py | 15 ++++++++------- titles/chuni/frontend.py | 4 ++-- titles/mai2/frontend.py | 6 +++--- titles/ongeki/frontend.py | 18 ++++++++++++------ 5 files changed, 26 insertions(+), 19 deletions(-) diff --git a/core/allnet.py b/core/allnet.py index 861a603..9eb6595 100644 --- a/core/allnet.py +++ b/core/allnet.py @@ -960,7 +960,7 @@ class DLReport: return True -cfg_dir = environ.get("DIANA_CFG_DIR", "config") +cfg_dir = environ.get("ARTEMIS_CFG_DIR", "config") cfg: CoreConfig = CoreConfig() if path.exists(f"{cfg_dir}/core.yaml"): cfg.update(yaml.safe_load(open(f"{cfg_dir}/core.yaml"))) diff --git a/core/frontend.py b/core/frontend.py index 31a6254..f15f58a 100644 --- a/core/frontend.py +++ b/core/frontend.py @@ -193,7 +193,7 @@ class FE_Base(): ), media_type="text/html; charset=utf-8") if sesh is None: - resp.delete_cookie("DIANA_SESH") + resp.delete_cookie("ARTEMIS_SESH") return resp def get_routes(self) -> List[Route]: @@ -218,6 +218,7 @@ class FE_Base(): sesh.permissions = tk['permissions'] sesh.chunithm_version = tk['chunithm_version'] sesh.maimai_version = tk['maimai_version'] + sesh.ongeki_version = tk['ongeki_version'] if sesh.user_id <= 0: self.logger.error("User session failed to validate due to an invalid ID!") @@ -243,7 +244,7 @@ class FE_Base(): return UserSession() def validate_session(self, request: Request) -> Optional[UserSession]: - sesh = request.cookies.get('DIANA_SESH', "") + sesh = request.cookies.get('ARTEMIS_SESH', "") if not sesh: return None @@ -304,7 +305,7 @@ class FE_Gate(FE_Base): error=err, sesh=vars(UserSession()), ), media_type="text/html; charset=utf-8") - resp.delete_cookie("DIANA_SESH") + resp.delete_cookie("ARTEMIS_SESH") return resp async def render_login(self, request: Request): @@ -354,7 +355,7 @@ class FE_Gate(FE_Base): usr_sesh = self.encode_session(sesh) self.logger.debug(f"Created session with JWT {usr_sesh}") resp = RedirectResponse("/user/", 303) - resp.set_cookie("DIANA_SESH", usr_sesh) + resp.set_cookie("ARTEMIS_SESH", usr_sesh) return resp @@ -393,7 +394,7 @@ class FE_Gate(FE_Base): usr_sesh = self.encode_session(sesh) self.logger.debug(f"Created session with JWT {usr_sesh}") resp = RedirectResponse("/user/", 303) - resp.set_cookie("DIANA_SESH", usr_sesh) + resp.set_cookie("ARTEMIS_SESH", usr_sesh) return resp @@ -511,7 +512,7 @@ class FE_User(FE_Base): async def render_logout(self, request: Request): resp = RedirectResponse("/gate/", 303) - resp.delete_cookie("DIANA_SESH") + resp.delete_cookie("ARTEMIS_SESH") return resp async def edit_card(self, request: Request) -> RedirectResponse: @@ -895,7 +896,7 @@ class FE_Machine(FE_Base): arcade={} ), media_type="text/html; charset=utf-8") -cfg_dir = environ.get("DIANA_CFG_DIR", "config") +cfg_dir = environ.get("ARTEMIS_CFG_DIR", "config") cfg: CoreConfig = CoreConfig() if path.exists(f"{cfg_dir}/core.yaml"): cfg.update(yaml.safe_load(open(f"{cfg_dir}/core.yaml"))) diff --git a/titles/chuni/frontend.py b/titles/chuni/frontend.py index b0fa9bc..0dbefac 100644 --- a/titles/chuni/frontend.py +++ b/titles/chuni/frontend.py @@ -68,7 +68,7 @@ class ChuniFrontend(FE_Base): if usr_sesh.chunithm_version >= 0: encoded_sesh = self.encode_session(usr_sesh) - resp.set_cookie("DIANA_SESH", encoded_sesh) + resp.set_cookie("ARTEMIS_SESH", encoded_sesh) return resp else: @@ -240,7 +240,7 @@ class ChuniFrontend(FE_Base): encoded_sesh = self.encode_session(usr_sesh) self.logger.info(f"Created session with JWT {encoded_sesh}") resp = RedirectResponse("/game/chuni/", 303) - resp.set_cookie("DIANA_SESH", encoded_sesh) + resp.set_cookie("ARTEMIS_SESH", encoded_sesh) return resp else: return RedirectResponse("/gate/", 303) \ No newline at end of file diff --git a/titles/mai2/frontend.py b/titles/mai2/frontend.py index 6ce8828..635c2fa 100644 --- a/titles/mai2/frontend.py +++ b/titles/mai2/frontend.py @@ -67,8 +67,8 @@ class Mai2Frontend(FE_Base): if usr_sesh.maimai_version >= 0: encoded_sesh = self.encode_session(usr_sesh) - resp.delete_cookie("DIANA_SESH") - resp.set_cookie("DIANA_SESH", encoded_sesh) + resp.delete_cookie("ARTEMIS_SESH") + resp.set_cookie("ARTEMIS_SESH", encoded_sesh) return resp else: @@ -184,7 +184,7 @@ class Mai2Frontend(FE_Base): encoded_sesh = self.encode_session(usr_sesh) self.logger.info(f"Created session with JWT {encoded_sesh}") resp = RedirectResponse("/game/mai2/", 303) - resp.set_cookie("DIANA_SESH", encoded_sesh) + resp.set_cookie("ARTEMIS_SESH", encoded_sesh) return resp else: return RedirectResponse("/gate/", 303) \ No newline at end of file diff --git a/titles/ongeki/frontend.py b/titles/ongeki/frontend.py index 1cdec03..226f318 100644 --- a/titles/ongeki/frontend.py +++ b/titles/ongeki/frontend.py @@ -31,7 +31,8 @@ class OngekiFrontend(FE_Base): def get_routes(self) -> List[Route]: return [ - Route("/", self.render_GET) + Route("/", self.render_GET), + Route("/version.change", self.render_POST, methods=['POST']) ] async def render_GET(self, request: Request) -> bytes: @@ -69,29 +70,34 @@ class OngekiFrontend(FE_Base): return RedirectResponse("/gate/", 303) async def render_POST(self, request: Request): - uri = request.uri.decode() + uri = request.url.path + frm = await request.form() usr_sesh = self.validate_session(request) if not usr_sesh: usr_sesh = UserSession() if usr_sesh.user_id > 0: if uri == "/game/ongeki/rival.add": - rival_id = request.args[b"rivalUserId"][0].decode() + rival_id = frm.get("rivalUserId") await self.data.profile.put_rival(usr_sesh.user_id, rival_id) # self.logger.info(f"{usr_sesh.user_id} added a rival") return RedirectResponse(b"/game/ongeki/", 303) elif uri == "/game/ongeki/rival.delete": - rival_id = request.args[b"rivalUserId"][0].decode() + rival_id = frm.get("rivalUserId") await self.data.profile.delete_rival(usr_sesh.user_id, rival_id) # self.logger.info(f"{response}") return RedirectResponse(b"/game/ongeki/", 303) elif uri == "/game/ongeki/version.change": - ongeki_version=request.args[b"version"][0].decode() + ongeki_version=frm.get("version") if(ongeki_version.isdigit()): usr_sesh.ongeki_version=int(ongeki_version) - return RedirectResponse("/game/ongeki/", 303) + enc = self.encode_session(usr_sesh) + resp = RedirectResponse("/game/ongeki/", 303) + resp.delete_cookie('ARTEMIS_SESH') + resp.set_cookie('ARTEMIS_SESH', enc) + return resp else: Response("Something went wrong", status_code=500) From e06e316b7d7670772bb6737c6d34db57f3ea4449 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Tue, 11 Jun 2024 10:30:57 -0400 Subject: [PATCH 26/76] pokken: fix achievement flags --- titles/pokken/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/titles/pokken/base.py b/titles/pokken/base.py index 562f3b9..c50654a 100644 --- a/titles/pokken/base.py +++ b/titles/pokken/base.py @@ -339,7 +339,7 @@ class PokkenBase: for ach_flg in req.achievement_flag: ach_flgs.append(ach_flg) - await self.data.profile.update_profile_tutorial_flags(user_id, ach_flg) + await self.data.profile.update_profile_achievement_flags(user_id, ach_flg) for evt_flg in req.event_achievement_flag: evt_flgs.append(evt_flg) From 3fd65da7fd8c0031008dd429fe5c0b63df1b8035 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Tue, 11 Jun 2024 12:25:45 -0400 Subject: [PATCH 27/76] pokken: backport changes from Diana --- titles/pokken/base.py | 159 +++++++++++++++++++++----------- titles/pokken/schema/profile.py | 98 ++++++++++++-------- 2 files changed, 161 insertions(+), 96 deletions(-) diff --git a/titles/pokken/base.py b/titles/pokken/base.py index c50654a..e22ad74 100644 --- a/titles/pokken/base.py +++ b/titles/pokken/base.py @@ -1,9 +1,7 @@ -from datetime import datetime, timedelta +from datetime import datetime import json, logging from typing import Any, Dict, List -import random -from core.data import Data from core import CoreConfig from .config import PokkenConfig from .proto import jackal_pb2 @@ -18,7 +16,6 @@ class PokkenBase: self.version = 0 self.logger = logging.getLogger("pokken") self.data = PokkenData(core_cfg) - self.SUPPORT_SET_NONE = 4294967295 async def handle_noop(self, request: Any) -> bytes: res = jackal_pb2.Response() @@ -38,7 +35,30 @@ class PokkenBase: res = jackal_pb2.Response() res.result = 1 res.type = jackal_pb2.MessageType.REGISTER_PCB - self.logger.info(f"Register PCB {request.register_pcb.pcb_id}") + pcbid = request.register_pcb.pcb_id + if not pcbid.isdigit() or len(pcbid) != 12 or \ + not pcbid.startswith(f"{PokkenConstants.SERIAL_IDENT[0]}{PokkenConstants.SERIAL_REGIONS[0]}{PokkenConstants.SERIAL_ROLES[0]}{PokkenConstants.SERIAL_CAB_IDENTS[0]}"): + self.logger.warn(f"Bad PCBID {pcbid}") + res.result = 0 + return res + + netid = PokkenConstants.NETID_PREFIX[0] + pcbid[5:] + + self.logger.info(f"Register PCB {pcbid} (netID {netid})") + + minfo = await self.data.arcade.get_machine(netid) + + if not minfo and not self.core_cfg.server.allow_unregistered_serials: + self.logger.warn(f"netID {netid} does not belong to any shop!") + res.result = 0 + return res + + elif not minfo: + self.logger.warn(f"Orphaned netID {netid} allowed to connect") + locid = 0 + + else: + locid = minfo['arcade'] regist_pcb = jackal_pb2.RegisterPcbResponseData() regist_pcb.server_time = int(datetime.now().timestamp()) @@ -46,7 +66,7 @@ class PokkenBase: "MatchingServer": { "host": f"https://{self.game_cfg.server.hostname}", "port": self.game_cfg.ports.game, - "url": "/SDAK/100/matching", + "url": "/pokken/matching", }, "StunServer": { "addr": self.game_cfg.server.stun_server_host, @@ -56,12 +76,12 @@ class PokkenBase: "addr": self.game_cfg.server.stun_server_host, "port": self.game_cfg.server.stun_server_port, }, - "AdmissionUrl": f"ws://{self.game_cfg.server.hostname}:{self.game_cfg.ports.admission}", - "locationId": 123, # FIXME: Get arcade's ID from the database - "logfilename": "JackalMatchingLibrary.log", - "biwalogfilename": "./biwa.log", + "AdmissionUrl": f"ws://{self.game_cfg.server.hostname}:{self.game_cfg.ports.admission}/pokken/admission", + "locationId": locid, + "logfilename": "J:\\JackalMatchingLibrary.log", + "biwalogfilename": "J:\\biwa_log.log", } - regist_pcb.bnp_baseuri = f"{self.core_cfg.server.hostname}/bna" + regist_pcb.bnp_baseuri = f"{self.core_cfg.mucha.hostname}/bna" regist_pcb.biwa_setting = json.dumps(biwa_setting) res.register_pcb.CopyFrom(regist_pcb) @@ -95,12 +115,11 @@ class PokkenBase: res.type = jackal_pb2.MessageType.LOAD_CLIENT_SETTINGS settings = jackal_pb2.LoadClientSettingsResponseData() - # TODO: Make configurable settings.money_magnification = 1 settings.continue_bonus_exp = 100 settings.continue_fight_money = 100 settings.event_bonus_exp = 100 - settings.level_cap = 999 + settings.level_cap = 100 settings.op_movie_flag = 0xFFFFFFFF settings.lucky_bonus_rate = 1 settings.fail_support_num = 10 @@ -132,9 +151,13 @@ class PokkenBase: res.type = jackal_pb2.MessageType.LOAD_USER access_code = request.load_user.access_code load_usr = jackal_pb2.LoadUserResponseData() - user_id = await self.data.card.get_user_id_from_card(access_code) + load_usr.load_hash = 1 + load_usr.access_code = access_code + load_usr.precedent_release_flag = 0xFFFFFFFF + load_usr.cardlock_status = False + card = await self.data.card.get_card_by_access_code(access_code) - if user_id is None and self.game_cfg.server.auto_register: + if card is None and self.game_cfg.server.auto_register: user_id = await self.data.user.create_user() card_id = await self.data.card.create_card(user_id, access_code) @@ -142,41 +165,27 @@ class PokkenBase: f"Register new card {access_code} (UserId {user_id}, CardId {card_id})" ) - elif user_id is None: + elif card is None: self.logger.info(f"Registration of card {access_code} blocked!") res.load_user.CopyFrom(load_usr) return res.SerializeToString() + + else: + user_id = card['user'] + card_id = card['id'] """ - TODO: Add repeated values - tutorial_progress_flag - rankmatch_progress + TODO: Unlock all supports? Probably support_pokemon_list - support_set_1 - support_set_2 - support_set_3 - aid_skill_list - achievement_flag - event_achievement_flag - event_achievement_param """ profile = await self.data.profile.get_profile(user_id) load_usr.commidserv_result = 1 - load_usr.load_hash = 1 - load_usr.cardlock_status = False load_usr.banapass_id = user_id - load_usr.access_code = access_code - load_usr.precedent_release_flag = 0xFFFFFFFF - if profile is None: + if profile is None or profile['trainer_name'] is None: profile_id = await self.data.profile.create_profile(user_id) profile_dict = {"id": profile_id, "user": user_id} pokemon_data = [] - tutorial_progress = [] - rankmatch_progress = [] - achievement_flag = [] - event_achievement_flag = [] - event_achievement_param = [] load_usr.new_card_flag = True else: @@ -185,11 +194,6 @@ class PokkenBase: f"Card-in user {user_id} (Trainer name {profile_dict.get('trainer_name', '')})" ) pokemon_data = await self.data.profile.get_all_pokemon_data(user_id) - tutorial_progress = [] - rankmatch_progress = [] - achievement_flag = [] - event_achievement_flag = [] - event_achievement_param = [] load_usr.new_card_flag = False load_usr.navi_newbie_flag = profile_dict.get("navi_newbie_flag", True) @@ -201,9 +205,9 @@ class PokkenBase: load_usr.trainer_name = profile_dict.get( "trainer_name", f"Newb{str(user_id).zfill(4)}" ) - load_usr.trainer_rank_point = profile_dict.get("trainer_rank_point", 0) - load_usr.wallet = profile_dict.get("wallet", 0) - load_usr.fight_money = profile_dict.get("fight_money", 0) + load_usr.trainer_rank_point = profile_dict.get("trainer_rank_point", 0) # determines rank + load_usr.wallet = profile_dict.get("wallet", 0) # pg count + load_usr.fight_money = profile_dict.get("fight_money", 0) # ? load_usr.score_point = profile_dict.get("score_point", 0) load_usr.grade_max_num = profile_dict.get("grade_max_num", 0) load_usr.extra_counter = profile_dict.get("extra_counter", 0) @@ -218,18 +222,18 @@ class PokkenBase: load_usr.rank_event = profile_dict.get("rank_event", 0) load_usr.awake_num = profile_dict.get("awake_num", 0) load_usr.use_support_num = profile_dict.get("use_support_num", 0) - load_usr.rankmatch_flag = profile_dict.get("rankmatch_flag", 0) + load_usr.rankmatch_flag = profile_dict.get("rankmatch_flag", 0) # flags that next rank match will be rank up load_usr.rankmatch_max = profile_dict.get("rankmatch_max", 0) load_usr.rankmatch_success = profile_dict.get("rankmatch_success", 0) load_usr.beat_num = profile_dict.get("beat_num", 0) - load_usr.title_text_id = profile_dict.get("title_text_id", 0) - load_usr.title_plate_id = profile_dict.get("title_plate_id", 0) - load_usr.title_decoration_id = profile_dict.get("title_decoration_id", 0) + load_usr.title_text_id = profile_dict.get("title_text_id", 2) + load_usr.title_plate_id = profile_dict.get("title_plate_id", 1) + load_usr.title_decoration_id = profile_dict.get("title_decoration_id", 1) load_usr.navi_trainer = profile_dict.get("navi_trainer", 0) load_usr.navi_version_id = profile_dict.get("navi_version_id", 0) load_usr.aid_skill = profile_dict.get("aid_skill", 0) - load_usr.comment_text_id = profile_dict.get("comment_text_id", 0) - load_usr.comment_word_id = profile_dict.get("comment_word_id", 0) + load_usr.comment_text_id = profile_dict.get("comment_text_id", 1) + load_usr.comment_word_id = profile_dict.get("comment_word_id", 1) load_usr.latest_use_pokemon = profile_dict.get("latest_use_pokemon", 0) load_usr.ex_ko_num = profile_dict.get("ex_ko_num", 0) load_usr.wko_num = profile_dict.get("wko_num", 0) @@ -237,11 +241,11 @@ class PokkenBase: load_usr.cool_ko_num = profile_dict.get("cool_ko_num", 0) load_usr.perfect_ko_num = profile_dict.get("perfect_ko_num", 0) load_usr.record_flag = profile_dict.get("record_flag", 0) - load_usr.site_register_status = profile_dict.get("site_register_status", 0) + load_usr.site_register_status = profile_dict.get("site_register_status", 1) load_usr.continue_num = profile_dict.get("continue_num", 0) load_usr.avatar_body = profile_dict.get("avatar_body", 0) - load_usr.avatar_gender = profile_dict.get("avatar_gender", 0) + load_usr.avatar_gender = profile_dict.get("avatar_gender", 1) load_usr.avatar_background = profile_dict.get("avatar_background", 0) load_usr.avatar_head = profile_dict.get("avatar_head", 0) load_usr.avatar_battleglass = profile_dict.get("avatar_battleglass", 0) @@ -283,6 +287,31 @@ class PokkenBase: pkm.bp_point_sp = pkmn_d.get('bp_point_sp', 0) load_usr.pokemon_data.append(pkm) + + for x in profile_dict.get("tutorial_progress_flag", []): + load_usr.tutorial_progress_flag.append(x) + + for x in profile_dict.get("achievement_flag", []): + load_usr.achievement_flag.append(x) + + for x in profile_dict.get("aid_skill_list", []): + load_usr.aid_skill_list.append(x) + + for x in profile_dict.get("rankmatch_progress", []): + load_usr.rankmatch_progress.append(x) + + for x in profile_dict.get("event_achievement_flag", []): + load_usr.event_achievement_flag.append(x) + + for x in profile_dict.get("event_achievement_param", []): + load_usr.event_achievement_param.append(x) + + load_usr.support_set_1.append(profile_dict.get("support_set_1_1", 4294967295)) + load_usr.support_set_1.append(profile_dict.get("support_set_1_2", 4294967295)) + load_usr.support_set_2.append(profile_dict.get("support_set_2_1", 4294967295)) + load_usr.support_set_2.append(profile_dict.get("support_set_2_2", 4294967295)) + load_usr.support_set_3.append(profile_dict.get("support_set_3_1", 4294967295)) + load_usr.support_set_3.append(profile_dict.get("support_set_3_2", 4294967295)) res.load_user.CopyFrom(load_usr) return res.SerializeToString() @@ -300,6 +329,8 @@ class PokkenBase: req = request.save_user user_id = req.banapass_id + + self.logger.info(f"Save user data for {user_id}") tut_flgs: List[int] = [] ach_flgs: List[int] = [] @@ -339,7 +370,7 @@ class PokkenBase: for ach_flg in req.achievement_flag: ach_flgs.append(ach_flg) - await self.data.profile.update_profile_achievement_flags(user_id, ach_flg) + await self.data.profile.update_profile_achievement_flags(user_id, ach_flgs) for evt_flg in req.event_achievement_flag: evt_flgs.append(evt_flg) @@ -353,18 +384,23 @@ class PokkenBase: await self.data.item.add_reward(user_id, reward.get_category_id, reward.get_content_id, reward.get_type_id) await self.data.profile.add_profile_points(user_id, get_rank_pts, get_money, get_score_pts, grade_max) + + # Inconsistant underscore use AND a typo?? + await self.data.profile.update_rankmatch_data(user_id, req.rankmatch_flag, req.rank_match_max, req.rank_match_success, req.rank_match_process) await self.data.profile.update_support_team(user_id, 1, req.support_set_1[0], req.support_set_1[1]) await self.data.profile.update_support_team(user_id, 2, req.support_set_2[0], req.support_set_2[1]) await self.data.profile.update_support_team(user_id, 3, req.support_set_3[0], req.support_set_3[1]) await self.data.profile.put_pokemon(user_id, mon.char_id, mon.illustration_book_no, mon.bp_point_atk, mon.bp_point_res, mon.bp_point_def, mon.bp_point_sp) - await self.data.profile.add_pokemon_xp(user_id, mon.char_id, mon.get_pokemon_exp) + await self.data.profile.add_pokemon_xp(user_id, mon.illustration_book_no, mon.get_pokemon_exp) + await self.data.profile.set_latest_mon(user_id, mon.illustration_book_no) for x in range(len(battle.play_mode)): + self.logger.info(f"Save {PokkenConstants.BATTLE_TYPE(battle.play_mode[x]).name} battle {PokkenConstants.BATTLE_RESULT(battle.result[x]).name} for {user_id} with mon {mon.illustration_book_no}") await self.data.profile.put_pokemon_battle_result( user_id, - mon.char_id, + mon.illustration_book_no, PokkenConstants.BATTLE_TYPE(battle.play_mode[x]), PokkenConstants.BATTLE_RESULT(battle.result[x]) ) @@ -391,7 +427,6 @@ class PokkenBase: last_evt ) - return res.SerializeToString() async def handle_save_ingame_log(self, data: jackal_pb2.Request) -> bytes: @@ -419,6 +454,13 @@ class PokkenBase: async def handle_matching_is_matching( self, data: Dict = {}, client_ip: str = "127.0.0.1" ) -> Dict: + """ + "sessionId":"12345678", + "A":{ + "pcb_id": data["data"]["must"]["pcb_id"], + "gip": client_ip + }, + """ return { "data": { "sessionId":"12345678", @@ -435,6 +477,11 @@ class PokkenBase: ) -> Dict: return {} + async def handle_matching_obtain_matching( + self, data: Dict = {}, client_ip: str = "127.0.0.1" + ) -> Dict: + return {} + async def handle_admission_noop(self, data: Dict, req_ip: str = "127.0.0.1") -> Dict: return {} diff --git a/titles/pokken/schema/profile.py b/titles/pokken/schema/profile.py index b7237de..38b93e7 100644 --- a/titles/pokken/schema/profile.py +++ b/titles/pokken/schema/profile.py @@ -1,11 +1,11 @@ from typing import Optional, Dict, List, Union from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_, case -from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON +from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, INTEGER from sqlalchemy.schema import ForeignKey from sqlalchemy.sql import func, select, update, delete from sqlalchemy.sql.functions import coalesce from sqlalchemy.engine import Row -from sqlalchemy.dialects.mysql import insert +from sqlalchemy.dialects.postgresql import insert from core.data.schema import BaseData, metadata from ..const import PokkenConstants @@ -16,13 +16,8 @@ profile = Table( "pokken_profile", metadata, Column("id", Integer, primary_key=True, nullable=False), - Column( - "user", - ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), - nullable=False, - unique=True, - ), - Column("trainer_name", String(16)), # optional + Column("user", Integer, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False, unique=True), + Column("trainer_name", String(14)), # optional Column("home_region_code", Integer), Column("home_loc_name", String(255)), Column("pref_code", Integer), @@ -105,20 +100,15 @@ profile = Table( Column("battle_num_vs_cpu", Integer), # 2 Column("win_cpu", Integer), Column("battle_num_tutorial", Integer), # 1? - mysql_charset="utf8mb4", ) pokemon_data = Table( - "pokken_pokemon_data", + "pokken_pokemon", metadata, Column("id", Integer, primary_key=True, nullable=False), - Column( - "user", - ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), - nullable=False, - ), - Column("char_id", Integer, nullable=False), - Column("illustration_book_no", Integer), + Column("user", Integer, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("char_id", Integer), + Column("illustration_book_no", Integer, nullable=False), # This is the fucking pokedex number???? Column("pokemon_exp", Integer), Column("battle_num_vs_wan", Integer), # 4? Column("win_vs_wan", Integer), @@ -132,8 +122,7 @@ pokemon_data = Table( Column("bp_point_res", Integer), Column("bp_point_def", Integer), Column("bp_point_sp", Integer), - UniqueConstraint("user", "char_id", name="pokken_pokemon_data_uk"), - mysql_charset="utf8mb4", + UniqueConstraint("user", "illustration_book_no", name="pokken_pokemon_uk"), ) @@ -157,8 +146,8 @@ class PokkenProfileData(BaseData): return result.lastrowid async def set_profile_name(self, user_id: int, new_name: str, gender: Union[int, None] = None) -> None: - sql = update(profile).where(profile.c.user == user_id).values( - trainer_name=new_name, + sql = profile.update(profile.c.user == user_id).values( + trainer_name=new_name if new_name else profile.c.trainer_name, avatar_gender=gender if gender is not None else profile.c.avatar_gender ) result = await self.execute(sql) @@ -179,12 +168,12 @@ class PokkenProfileData(BaseData): aid_skill: int, last_evt: int ) -> None: - sql = update(profile).where(profile.c.user == user_id).values( + sql = profile.update(profile.c.user == user_id).values( extra_counter=extra_counter, event_reward_get_flag=evt_reward_get_flg, - total_play_days=total_play_days, - awake_num=awake_num, - use_support_num=use_support_ct, + total_play_days=coalesce(profile.c.total_play_days, 0) + total_play_days, + awake_num=coalesce(profile.c.awake_num, 0) + awake_num, + use_support_num=coalesce(profile.c.use_support_num, 0) + use_support_ct, beat_num=beat_num, aid_skill=aid_skill, last_play_event_id=last_evt @@ -195,7 +184,7 @@ class PokkenProfileData(BaseData): self.logger.error(f"Failed to put extra data for user {user_id}") async def update_profile_tutorial_flags(self, user_id: int, tutorial_flags: List) -> None: - sql = update(profile).where(profile.c.user == user_id).values( + sql = profile.update(profile.c.user == user_id).values( tutorial_progress_flag=tutorial_flags, ) result = await self.execute(sql) @@ -205,7 +194,7 @@ class PokkenProfileData(BaseData): ) async def update_profile_achievement_flags(self, user_id: int, achievement_flags: List) -> None: - sql = update(profile).where(profile.c.user == user_id).values( + sql = profile.update(profile.c.user == user_id).values( achievement_flag=achievement_flags, ) result = await self.execute(sql) @@ -215,7 +204,7 @@ class PokkenProfileData(BaseData): ) async def update_profile_event(self, user_id: int, event_state: List, event_flags: List[int], event_param: List[int], last_evt: int = None) -> None: - sql = update(profile).where(profile.c.user == user_id).values( + sql = profile.update(profile.c.user == user_id).values( event_state=event_state, event_achievement_flag=event_flags, event_achievement_param=event_param, @@ -230,12 +219,16 @@ class PokkenProfileData(BaseData): async def add_profile_points( self, user_id: int, rank_pts: int, money: int, score_pts: int, grade_max: int ) -> None: - sql = update(profile).where(profile.c.user == user_id).values( - trainer_rank_point = profile.c.trainer_rank_point + rank_pts, - fight_money = profile.c.fight_money + money, - score_point = profile.c.score_point + score_pts, + sql = profile.update(profile.c.user == user_id).values( + trainer_rank_point = coalesce(profile.c.trainer_rank_point, 0) + rank_pts, + wallet = coalesce(profile.c.wallet, 0) + money, + score_point = coalesce(profile.c.score_point, 0) + score_pts, grade_max_num = grade_max ) + + result = await self.execute(sql) + if result is None: + return None async def get_profile(self, user_id: int) -> Optional[Row]: sql = profile.select(profile.c.user == user_id) @@ -248,7 +241,7 @@ class PokkenProfileData(BaseData): self, user_id: int, pokemon_id: int, - illust_no: int, + pokedex_number: int, atk: int, res: int, defe: int, @@ -257,7 +250,7 @@ class PokkenProfileData(BaseData): sql = insert(pokemon_data).values( user=user_id, char_id=pokemon_id, - illustration_book_no=illust_no, + illustration_book_no=pokedex_number, pokemon_exp=0, battle_num_vs_wan=0, win_vs_wan=0, @@ -274,7 +267,7 @@ class PokkenProfileData(BaseData): ) conflict = sql.on_duplicate_key_update( - illustration_book_no=illust_no, + illustration_book_no=pokedex_number, bp_point_atk=pokemon_data.c.bp_point_atk + atk, bp_point_res=pokemon_data.c.bp_point_res + res, bp_point_def=pokemon_data.c.bp_point_def + defe, @@ -293,7 +286,7 @@ class PokkenProfileData(BaseData): pokemon_id: int, xp: int ) -> None: - sql = update(pokemon_data).where(and_(pokemon_data.c.user==user_id, pokemon_data.c.char_id==pokemon_id)).values( + sql = pokemon_data.update(and_(pokemon_data.c.user==user_id, pokemon_data.c.illustration_book_no==pokemon_id)).values( pokemon_exp=coalesce(pokemon_data.c.pokemon_exp, 0) + xp ) @@ -315,6 +308,14 @@ class PokkenProfileData(BaseData): return None return result.fetchall() + async def set_latest_mon(self, user_id: int, pokedex_no: int) -> None: + sql = profile.update(profile.c.user == user_id).values( + latest_use_pokemon=pokedex_no + ) + result = await self.execute(sql) + if result is None: + self.logger.warning(f"Failed to update user {user_id}'s last used pokemon {pokedex_no}") + async def put_pokemon_battle_result( self, user_id: int, pokemon_id: int, match_type: PokkenConstants.BATTLE_TYPE, match_result: PokkenConstants.BATTLE_RESULT ) -> None: @@ -322,7 +323,7 @@ class PokkenProfileData(BaseData): Records the match stats (type and win/loss) for the pokemon and profile coalesce(pokemon_data.c.win_vs_wan, 0) """ - sql = update(pokemon_data).where(and_(pokemon_data.c.user==user_id, pokemon_data.c.char_id==pokemon_id)).values( + sql = pokemon_data.update(and_(pokemon_data.c.user==user_id, pokemon_data.c.char_id==pokemon_id)).values( battle_num_tutorial=coalesce(pokemon_data.c.battle_num_tutorial, 0) + 1 if match_type==PokkenConstants.BATTLE_TYPE.TUTORIAL else coalesce(pokemon_data.c.battle_num_tutorial, 0), battle_all_num_tutorial=coalesce(pokemon_data.c.battle_all_num_tutorial, 0) + 1 if match_type==PokkenConstants.BATTLE_TYPE.TUTORIAL else coalesce(pokemon_data.c.battle_all_num_tutorial, 0), @@ -353,7 +354,7 @@ class PokkenProfileData(BaseData): """ Records profile stats """ - sql = update(profile).where(profile.c.user==user_id).values( + sql = profile.update(profile.c.user==user_id).values( ex_ko_num=coalesce(profile.c.ex_ko_num, 0) + exkos, wko_num=coalesce(profile.c.wko_num, 0) + wkos, timeup_win_num=coalesce(profile.c.timeup_win_num, 0) + timeout_wins, @@ -367,7 +368,12 @@ class PokkenProfileData(BaseData): self.logger.warning(f"Failed to update stats for user {user_id}") async def update_support_team(self, user_id: int, support_id: int, support1: int = None, support2: int = None) -> None: - sql = update(profile).where(profile.c.user==user_id).values( + if support1 == 4294967295: + support1 = None + + if support2 == 4294967295: + support2 = None + sql = profile.update(profile.c.user==user_id).values( support_set_1_1=support1 if support_id == 1 else profile.c.support_set_1_1, support_set_1_2=support2 if support_id == 1 else profile.c.support_set_1_2, support_set_2_1=support1 if support_id == 2 else profile.c.support_set_2_1, @@ -379,3 +385,15 @@ class PokkenProfileData(BaseData): result = await self.execute(sql) if result is None: self.logger.warning(f"Failed to update support team {support_id} for user {user_id}") + + async def update_rankmatch_data(self, user_id: int, flag: int, rm_max: Optional[int], success: Optional[int], progress: List[int]) -> None: + sql = profile.update(profile.c.user==user_id).values( + rankmatch_flag=flag, + rankmatch_max=rm_max, + rankmatch_progress=progress, + rankmatch_success=success, + ) + + result = await self.execute(sql) + if result is None: + self.logger.warning(f"Failed to update rankmatch data for user {user_id}") From fe4d978f7025c6565e54f8c838ad8b00f423e3db Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Tue, 11 Jun 2024 13:56:39 -0400 Subject: [PATCH 28/76] pokken: fix bnp_baseuri --- titles/pokken/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/titles/pokken/base.py b/titles/pokken/base.py index e22ad74..5830f43 100644 --- a/titles/pokken/base.py +++ b/titles/pokken/base.py @@ -81,7 +81,7 @@ class PokkenBase: "logfilename": "J:\\JackalMatchingLibrary.log", "biwalogfilename": "J:\\biwa_log.log", } - regist_pcb.bnp_baseuri = f"{self.core_cfg.mucha.hostname}/bna" + regist_pcb.bnp_baseuri = f"{self.core_cfg.server.hostname}/bna" regist_pcb.biwa_setting = json.dumps(biwa_setting) res.register_pcb.CopyFrom(regist_pcb) From eaab3728c45d36be0b6b47107cb94ec0f84c6d7f Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Tue, 11 Jun 2024 14:00:29 -0400 Subject: [PATCH 29/76] pokken: add additional logging --- titles/pokken/base.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/titles/pokken/base.py b/titles/pokken/base.py index 5830f43..199d226 100644 --- a/titles/pokken/base.py +++ b/titles/pokken/base.py @@ -184,15 +184,14 @@ class PokkenBase: if profile is None or profile['trainer_name'] is None: profile_id = await self.data.profile.create_profile(user_id) + self.logger.info(f"Create new profile {profile_id} for user {user_id}") profile_dict = {"id": profile_id, "user": user_id} pokemon_data = [] load_usr.new_card_flag = True else: profile_dict = {k: v for k, v in profile._asdict().items() if v is not None} - self.logger.info( - f"Card-in user {user_id} (Trainer name {profile_dict.get('trainer_name', '')})" - ) + self.logger.info(f"Card-in user {user_id} (Trainer name {profile_dict.get('trainer_name', '')})") pokemon_data = await self.data.profile.get_all_pokemon_data(user_id) load_usr.new_card_flag = False From c3efc36be2556551afaa8a88504bfe6b52eb8a7c Mon Sep 17 00:00:00 2001 From: beerpsi Date: Wed, 12 Jun 2024 16:08:12 +0700 Subject: [PATCH 30/76] [chuni] Add correct endpoint `iter_count`s for all versions with encryption --- titles/chuni/index.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/titles/chuni/index.py b/titles/chuni/index.py index f0f1eac..e030004 100644 --- a/titles/chuni/index.py +++ b/titles/chuni/index.py @@ -107,8 +107,23 @@ class ChuniServlet(BaseServlet): iter_count = 36 elif version == ChuniConstants.VER_CHUNITHM_SUN: iter_count = 70 - else: + elif version == ChuniConstants.VER_CHUNITHM_NEW_PLUS: + iter_count = 25 + elif version == ChuniConstants.VER_CHUNITHM_NEW: + iter_count = 54 + elif version == ChuniConstants.VER_CHUNITHM_PARADISE: iter_count = 44 + elif version == ChuniConstants.VER_CHUNITHM_CRYSTAL_PLUS: + iter_count = 67 + else: + disp_version = ( + ChuniConstants.VERSION_NAMES[version] + if version in ChuniConstants.VERSION_NAMES + else str(version) + ) + + self.logger.warning("Version %s does not support encryption", disp_version) + continue hash = PBKDF2( method_fixed, @@ -313,4 +328,4 @@ class ChuniServlet(BaseServlet): bytes.fromhex(self.game_cfg.crypto.keys[internal_ver][1]), ) - return Response(crypt.encrypt(padded)) \ No newline at end of file + return Response(crypt.encrypt(padded)) From e21568cfd96461506438fdbb24073d964d84167a Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Wed, 12 Jun 2024 23:24:45 -0400 Subject: [PATCH 31/76] pokken: fix profile loading fail --- core/utils.py | 2 +- titles/pokken/base.py | 2 +- titles/pokken/schema/profile.py | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/utils.py b/core/utils.py index a9d1705..24c174c 100644 --- a/core/utils.py +++ b/core/utils.py @@ -49,7 +49,7 @@ class Utils: def get_title_port_ssl(cls, cfg: CoreConfig): if cls.real_title_port_ssl is not None: return cls.real_title_port_ssl - cls.real_title_port_ssl = cfg.server.proxy_port_ssl if cfg.server.is_using_proxy and cfg.server.proxy_port_ssl else Utils.get_title_port(cfg) + cls.real_title_port_ssl = cfg.server.proxy_port_ssl if cfg.server.is_using_proxy and cfg.server.proxy_port_ssl else 443 return cls.real_title_port_ssl diff --git a/titles/pokken/base.py b/titles/pokken/base.py index 199d226..de974a3 100644 --- a/titles/pokken/base.py +++ b/titles/pokken/base.py @@ -385,7 +385,7 @@ class PokkenBase: await self.data.profile.add_profile_points(user_id, get_rank_pts, get_money, get_score_pts, grade_max) # Inconsistant underscore use AND a typo?? - await self.data.profile.update_rankmatch_data(user_id, req.rankmatch_flag, req.rank_match_max, req.rank_match_success, req.rank_match_process) + #await self.data.profile.update_rankmatch_data(user_id, req.rankmatch_flag, req.rank_match_max, req.rank_match_success, req.rank_match_process) await self.data.profile.update_support_team(user_id, 1, req.support_set_1[0], req.support_set_1[1]) await self.data.profile.update_support_team(user_id, 2, req.support_set_2[0], req.support_set_2[1]) diff --git a/titles/pokken/schema/profile.py b/titles/pokken/schema/profile.py index 38b93e7..f66ebb6 100644 --- a/titles/pokken/schema/profile.py +++ b/titles/pokken/schema/profile.py @@ -5,7 +5,7 @@ from sqlalchemy.schema import ForeignKey from sqlalchemy.sql import func, select, update, delete from sqlalchemy.sql.functions import coalesce from sqlalchemy.engine import Row -from sqlalchemy.dialects.postgresql import insert +from sqlalchemy.dialects.mysql import insert from core.data.schema import BaseData, metadata from ..const import PokkenConstants @@ -103,7 +103,7 @@ profile = Table( ) pokemon_data = Table( - "pokken_pokemon", + "pokken_pokemon_data", metadata, Column("id", Integer, primary_key=True, nullable=False), Column("user", Integer, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), @@ -295,7 +295,7 @@ class PokkenProfileData(BaseData): self.logger.warning(f"Failed to add {xp} XP to pokemon ID {pokemon_id} for user {user_id}") async def get_pokemon_data(self, user_id: int, pokemon_id: int) -> Optional[Row]: - sql = pokemon_data.select(and_(pokemon_data.c.user == user_id, pokemon_data.c.char_id == pokemon_id)) + sql = pokemon_data.select(and_(pokemon_data.c.user == user_id, pokemon_data.c.illustration_book_no == pokemon_id)) result = await self.execute(sql) if result is None: return None @@ -323,7 +323,7 @@ class PokkenProfileData(BaseData): Records the match stats (type and win/loss) for the pokemon and profile coalesce(pokemon_data.c.win_vs_wan, 0) """ - sql = pokemon_data.update(and_(pokemon_data.c.user==user_id, pokemon_data.c.char_id==pokemon_id)).values( + sql = pokemon_data.update(and_(pokemon_data.c.user==user_id, pokemon_data.c.illustration_book_no==pokemon_id)).values( battle_num_tutorial=coalesce(pokemon_data.c.battle_num_tutorial, 0) + 1 if match_type==PokkenConstants.BATTLE_TYPE.TUTORIAL else coalesce(pokemon_data.c.battle_num_tutorial, 0), battle_all_num_tutorial=coalesce(pokemon_data.c.battle_all_num_tutorial, 0) + 1 if match_type==PokkenConstants.BATTLE_TYPE.TUTORIAL else coalesce(pokemon_data.c.battle_all_num_tutorial, 0), From ee7d5c4e217dae3c92d56ce6972c9a0c1b6b2f22 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Thu, 13 Jun 2024 14:14:07 -0400 Subject: [PATCH 32/76] pokken: readd mysql_charset to profile and pokemon data --- titles/pokken/data/fighters.json | Bin 0 -> 5504 bytes titles/pokken/data/support.json | Bin 0 -> 10 bytes titles/pokken/schema/profile.py | 4 +++- 3 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 titles/pokken/data/fighters.json create mode 100644 titles/pokken/data/support.json diff --git a/titles/pokken/data/fighters.json b/titles/pokken/data/fighters.json new file mode 100644 index 0000000000000000000000000000000000000000..cc22c32bd816a508f7328031d0d4da5c23355d14 GIT binary patch literal 5504 zcmd6rOK;Oa5XbLG{SHy2-e{aSj??tk2da=-sZtLdtBtdbmn2@xk3Lliya;GH021QB zfk%0Wwm=aTFTuCjFT&U{RTg$PT#%bnVo&7#WoG{KpX^RtD#5F4S<~g2((dq6cEv*j zyoNovT3(7B6cAdz{O9fKetMg;6P}!KcFdDwAtu`PF$7dczlVZolLo!?du|l@;f!Gf zXtUBKksG(;5C;zRBJ4#KhYpMt+QYLeSB=mQNQ6UUfWim|=O2BzJ)oXJBNwjH^>yM9 z)St>9VE|Lb_qyfbsrT|zl+6Y_TtT`*ddP8Ofg<}Mev>}RNN;lXnkR3Bu!3RDhK$wL zWOW%x(ZClVMp+@H$*U}@?>YM^gypQYf)Z=X^Ez#+6T66l4&I<75I6+f&p^r9K?Z$S z2+MKJf^f}gQb`=uu&$M{wk$R5BKEpS z)3BcakSFgH;#5?(wk#Q^*=VXk=lW=e^kCMGww#dE2R!-8*~=`iheBB4W2Y&@wW?-I z&6`iz(h_M0>ZS#WO8XX?7ApA%XJbe2IK@JNtH4%g&r48@65dx2Ci)e_Ibtxvx6dk93-ye9LUd za4buOY}_yKznqrYkQTjN)f53pnb*PnSW^Lc&7X00M=_)d)3RzyR`#_M|~sJw6w{`;*S&be{1ii(L_RH56`(a zJg1D)(m4c(DA;22G8@{u#>Tfl`kCqh`!+fkz~!>zY8+tHPQPy3jv?Pc;d zb<*rf`bQ3vuD(T$9^jJUHcgF2EntUqV!W+(mO~?zulf%!m{0+m4B4L62-Pg^lCY0L zS147XqCaH+(m_!}iuZ-Ef~5+;WV{;Z2? AxBvhE literal 0 HcmV?d00001 diff --git a/titles/pokken/data/support.json b/titles/pokken/data/support.json new file mode 100644 index 0000000000000000000000000000000000000000..a514dfe8a033285b938e7e0dd7e20e43c993f8e3 GIT binary patch literal 10 Pcmb>C Date: Thu, 13 Jun 2024 18:06:16 -0400 Subject: [PATCH 33/76] pokken: finish filling out support list --- titles/pokken/data/support.json | Bin 10 -> 12133 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/titles/pokken/data/support.json b/titles/pokken/data/support.json index a514dfe8a033285b938e7e0dd7e20e43c993f8e3..ef9f07933a71b32c6344ccca55f74cfd9adda2bd 100644 GIT binary patch literal 12133 zcmdU#U2ojR6^8EvYdmVZ$xvtqEZ17)Km-~hp0IPgO*WBQEHlDnWg0Qgtmav7lz^j+ z()z89Zc0a~JezrM-$V3^a{k}4_@mBsUN{%`LZkK0kM zCyX0tis7ILZ5$P2c?4OFdBP)-6_FCDNM@DjI6J39te`=TpXm)6=(IuG+z1r1J*^@> zmO^_>n%w4p$9Y5{E9d_NJ?}CP-3h&Sc9_Q~P*B`LT@$UKIyRPIn2rP+&ghI1M8}E^ z^EejPtgzk5M2xHi$MdO3nXy`;Rz%FbK&+ZAU*c+fny&*&@{ ze=O(EylXtL_eGo$iTkb}C^i*5b`qqLex*ssbkQUr(pQH(Q|G-3v$YPcZ3pV$8&dPp zRC-uEbSQv0(V+LlyT(IzBF&VGwl__JVVfp5)&pn$WGWLQN6K1{oJSW1=4FY7On?L3_$VJ5D{XGw znFqQtNzyeIQytyf%@_u`#=-z5dX1;y-r#{!8@#pc?k4j?nF`DCKDyEUwJbg>=YOH9 z8A5j=yT*X>41K@o3MqL$ zKC(S}#kK7=$m=(6byGf&AQ8#Dn{x39Auc|uypVS@ybACzk*UfJJCWpYF4_|Dtd)7C z1~f~kqkai@Z+Zo@Hk8|e{06Kkdw7{iJU+Y_eqxwi$m3X>oYO6poe7`I{@hJmSZmC^F@Vg5^Aet4wI{ST8z{g&>NKq*OBeCH|ec!wswiZdO&*jG>=q@ z^ukkDQ-1T8?u@>mj$C}>UFL}puQXEZKvKP8|6aiLT*n@FQdj?R42`vsP|3fG=LNw%pFTudv5yEb&WSL`JKI#{ANT_+ z?`80&McUg9U%$29dc#9;+g+`Al&7cOt@IAZS7q^qt1S57rFWTkIF8jja@6onb^E&F zo4kC|bu%OSL)MKx^lfQeYbvLj1k zUg%T1%PT<LBRH)d#WrXiBnQD62LsM2*I6e!O5L8h=8q4)@-Fk#wgYF0WDV?) zkm)>w?qHZ)K6z1xtD=SyOaWZOf;Mp?AO=ZzRC)~xoUkSsD%0Bu2E8^#4sHax+}(VL z-HFGlDM$DL3V`YV-Zhta!g>nLK>$oUa!d*_sIg?oF9~QB2h09i1hBNx+6a`pdon`y z+8qKFzjB2R-QnlmwD2OAF%1Qd^D=`YAQfR_HFHy2PLW@zYA+ZGdGH0 zra0yHJlaDD_BKTXDBkO(;6Az5@U5W=1itR$ngArRfOYNI2#T{0ekqV(-xRcFG60 zHrqJ|?9Ah2s6(K!r=gVtMsW<}0bw4cTw_Cn-Z3;v51aiK4cQxF9P~_+K3&^JiKK+d41VoFyVlX4xiF3jBV$9r)vyM#-m(kQxQ*mX| zX_Yg#q?zQ47=493sN{I>#|&8qrE$wNIvS7 z+LQS*_PjoGyxzBlIMB8c-wKUBKRFXI_CQ|2ZMH9phW|`4F+Hvkumlf>u{@)Q7z>Ik zMvB0jQ!?!Ihz2s0I*zFOT%(*+Ysl}M+5x$Z{n~aQq2Gt?QGw7DHQL2k@DHKTbU&vj zp76v1@@A?lSMaJZ9xkF=PAbHj0g(w za09!4*jr}=t#*ikl6{CX981+nkp>DpaD!FT3-21w)$QcNA>!#B!4r~YGmovA)dMWm zkO-3l9xG&HmD7*Cl_wd9N_+^*c~%+W~_Vkhf{;w#jcJP%hlG2Jpk9((3&qbIV}t7rr9Xbi`W{MetX>q zc(+PKnVD8S>Rvgn?LKm0f#u#?2*bN_sq^Bc%kPPWSRpt%$hUDLouaQ zS92;}2>OyS=}*gBty&&z1e)4`IQPgj%>>b-M@qa4yh3n5PfO)73G&cYLuyhkCIGdI zQqZNzWv_D3Cdbon-4eYq#I0#ok>^B+AYNV0_;`$k4+R)?PLW{;-pDC>yDPxoCheP{ zyK)zgEDgJtX5^=_Lc!`RFnS-;czmq!h-l2d_Xf{+DvY?z$|s+fPw>#i@5(11m-An< z#%159Vs0?cjHZ}R>^W9X>~M-Fzv)Rd)R{yXdOCdO*rRe|m6qCO%27F%D93Xc^t{!g Q2lPfBh@0eJf9<{h1NzNw{{R30 literal 10 Pcmb>C Date: Thu, 13 Jun 2024 18:15:38 -0400 Subject: [PATCH 34/76] pokken: fix some image links --- titles/pokken/data/fighters.json | Bin 5504 -> 5487 bytes titles/pokken/data/support.json | Bin 12133 -> 12137 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/titles/pokken/data/fighters.json b/titles/pokken/data/fighters.json index cc22c32bd816a508f7328031d0d4da5c23355d14..90681ec29853d1af0b3227b993c6aa7a2979e449 100644 GIT binary patch delta 26 icmZqBey_FR4aemBToJ5>1_l-;lN(q?Hs^5)@c;mdxd^iW delta 93 zcmaE_)u6rM4aekNJZc^#8Kt>N`eyoO#`*#I+1aYj#;T5%dGP_6*@?*+rSU$QNkyr8 a1$pWEW@g3(6}mXpOr9ttvN@Jhhz9@%zaOsv diff --git a/titles/pokken/data/support.json b/titles/pokken/data/support.json index ef9f07933a71b32c6344ccca55f74cfd9adda2bd..ef5368ef85f7931d28e043965d7b8a6440573eef 100644 GIT binary patch delta 29 fcmaDF_cCrnpC+rJfq}W@=82lQj8G<{b_xpsq=*S1 delta 33 lcmaDE_cU%ppC+@hg~{YZ9huGRH3J#JoR4avo29h Date: Fri, 14 Jun 2024 00:06:39 -0400 Subject: [PATCH 35/76] pokken: fix pokemon_data uk --- .../3657efefc5a4_pokken_fix_pokemon_uk.py | 54 ++++++++++++++++++ titles/pokken/base.py | 12 ++-- titles/pokken/data/fighters.json | Bin 5487 -> 5488 bytes 3 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 core/data/alembic/versions/3657efefc5a4_pokken_fix_pokemon_uk.py diff --git a/core/data/alembic/versions/3657efefc5a4_pokken_fix_pokemon_uk.py b/core/data/alembic/versions/3657efefc5a4_pokken_fix_pokemon_uk.py new file mode 100644 index 0000000..2867fb7 --- /dev/null +++ b/core/data/alembic/versions/3657efefc5a4_pokken_fix_pokemon_uk.py @@ -0,0 +1,54 @@ +"""pokken_fix_pokemon_uk + +Revision ID: 3657efefc5a4 +Revises: 4a02e623e5e6 +Create Date: 2024-06-13 23:50:57.611998 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = '3657efefc5a4' +down_revision = '4a02e623e5e6' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('pokken_pokemon_data', 'char_id', + existing_type=mysql.INTEGER(display_width=11), + nullable=True) + op.alter_column('pokken_pokemon_data', 'illustration_book_no', + existing_type=mysql.INTEGER(display_width=11), + nullable=False) + op.drop_constraint('pokken_pokemon_data_ibfk_1', table_name='pokken_pokemon_data', type_='foreignkey') + op.drop_index('pokken_pokemon_data_uk', table_name='pokken_pokemon_data') + op.create_unique_constraint('pokken_pokemon_uk', 'pokken_pokemon_data', ['user', 'illustration_book_no']) + op.create_foreign_key("pokken_pokemon_data_ibfk_1", "pokken_pokemon_data", "aime_user", ['user'], ['id']) + op.alter_column('pokken_profile', 'trainer_name', + existing_type=mysql.VARCHAR(length=16), + type_=sa.String(length=14), + existing_nullable=True) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('pokken_profile', 'trainer_name', + existing_type=sa.String(length=14), + type_=mysql.VARCHAR(length=16), + existing_nullable=True) + op.drop_constraint('pokken_pokemon_data_ibfk_1', table_name='pokken_pokemon_data', type_='foreignkey') + op.drop_constraint('pokken_pokemon_uk', 'pokken_pokemon_data', type_='unique') + op.create_index('pokken_pokemon_data_uk', 'pokken_pokemon_data', ['user', 'char_id'], unique=True) + op.create_foreign_key("pokken_pokemon_data_ibfk_1", "pokken_pokemon_data", "aime_user", ['user'], ['id']) + op.alter_column('pokken_pokemon_data', 'illustration_book_no', + existing_type=mysql.INTEGER(display_width=11), + nullable=True) + op.alter_column('pokken_pokemon_data', 'char_id', + existing_type=mysql.INTEGER(display_width=11), + nullable=False) + # ### end Alembic commands ### diff --git a/titles/pokken/base.py b/titles/pokken/base.py index de974a3..fe11e99 100644 --- a/titles/pokken/base.py +++ b/titles/pokken/base.py @@ -305,12 +305,12 @@ class PokkenBase: for x in profile_dict.get("event_achievement_param", []): load_usr.event_achievement_param.append(x) - load_usr.support_set_1.append(profile_dict.get("support_set_1_1", 4294967295)) - load_usr.support_set_1.append(profile_dict.get("support_set_1_2", 4294967295)) - load_usr.support_set_2.append(profile_dict.get("support_set_2_1", 4294967295)) - load_usr.support_set_2.append(profile_dict.get("support_set_2_2", 4294967295)) - load_usr.support_set_3.append(profile_dict.get("support_set_3_1", 4294967295)) - load_usr.support_set_3.append(profile_dict.get("support_set_3_2", 4294967295)) + load_usr.support_set_1.append(profile_dict.get("support_set_1_1", 587)) + load_usr.support_set_1.append(profile_dict.get("support_set_1_2", 653)) + load_usr.support_set_2.append(profile_dict.get("support_set_2_1", 495)) + load_usr.support_set_2.append(profile_dict.get("support_set_2_2", 131)) + load_usr.support_set_3.append(profile_dict.get("support_set_3_1", 657)) + load_usr.support_set_3.append(profile_dict.get("support_set_3_2", 133)) res.load_user.CopyFrom(load_usr) return res.SerializeToString() diff --git a/titles/pokken/data/fighters.json b/titles/pokken/data/fighters.json index 90681ec29853d1af0b3227b993c6aa7a2979e449..a5ba6f850cf8430e950e931b0519f520addcdae2 100644 GIT binary patch delta 26 icmaE_^+9VxI0vhtfq{|flld9hH`j1{<^ceG*9flw delta 25 hcmeyM^ Date: Sat, 15 Jun 2024 22:22:07 +0700 Subject: [PATCH 36/76] support SDGS encryption --- titles/chuni/index.py | 88 +++++++++++++++++++++++++++---------------- 1 file changed, 55 insertions(+), 33 deletions(-) diff --git a/titles/chuni/index.py b/titles/chuni/index.py index e030004..726f02a 100644 --- a/titles/chuni/index.py +++ b/titles/chuni/index.py @@ -39,7 +39,7 @@ class ChuniServlet(BaseServlet): def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None: super().__init__(core_cfg, cfg_dir) self.game_cfg = ChuniConfig() - self.hash_table: Dict[Dict[str, str]] = {} + self.hash_table: Dict[str, Dict[str, str]] = {} if path.exists(f"{cfg_dir}/{ChuniConstants.CONFIG_NAME}"): self.game_cfg.update( yaml.safe_load(open(f"{cfg_dir}/{ChuniConstants.CONFIG_NAME}")) @@ -89,45 +89,59 @@ class ChuniServlet(BaseServlet): ) self.logger.inited = True + known_iter_counts = { + ChuniConstants.VER_CHUNITHM_CRYSTAL_PLUS: 67, + ChuniConstants.VER_CHUNITHM_PARADISE: 44, + f"{ChuniConstants.VER_CHUNITHM_PARADISE}_int": 25, + ChuniConstants.VER_CHUNITHM_NEW: 54, + f"{ChuniConstants.VER_CHUNITHM_NEW}_int": 49, + ChuniConstants.VER_CHUNITHM_NEW_PLUS: 25, + ChuniConstants.VER_CHUNITHM_SUN: 70, + ChuniConstants.VER_CHUNITHM_SUN_PLUS: 36, + } + for version, keys in self.game_cfg.crypto.keys.items(): if len(keys) < 3: continue - self.hash_table[version] = {} + if isinstance(version, int): + version_idx = version + else: + version_idx = int(version.split("_")[0]) + + salt = bytes.fromhex(keys[2]) + if len(keys) >= 4: + iter_count = keys[3] + elif (iter_count := known_iter_counts.get(version)) is None: + self.logger.error( + "Number of iteration rounds for version %s is not known, but it is not specified in the config", + version, + ) + continue + + self.hash_table[version] = {} method_list = [ method - for method in dir(self.versions[version]) + for method in dir(self.versions[version_idx]) if not method.startswith("__") ] + for method in method_list: method_fixed = inflection.camelize(method)[6:-7] - # number of iterations was changed to 70 in SUN and then to 36 - if version == ChuniConstants.VER_CHUNITHM_SUN_PLUS: - iter_count = 36 - elif version == ChuniConstants.VER_CHUNITHM_SUN: - iter_count = 70 - elif version == ChuniConstants.VER_CHUNITHM_NEW_PLUS: - iter_count = 25 - elif version == ChuniConstants.VER_CHUNITHM_NEW: - iter_count = 54 - elif version == ChuniConstants.VER_CHUNITHM_PARADISE: - iter_count = 44 - elif version == ChuniConstants.VER_CHUNITHM_CRYSTAL_PLUS: - iter_count = 67 - else: - disp_version = ( - ChuniConstants.VERSION_NAMES[version] - if version in ChuniConstants.VERSION_NAMES - else str(version) - ) - - self.logger.warning("Version %s does not support encryption", disp_version) - continue + + # This only applies for CHUNITHM NEW International and later for some reason. + # CHUNITHM SUPERSTAR (PLUS) did not add "Exp" to the endpoint when hashing. + if ( + isinstance(version, str) + and version.endswith("_int") + and version_idx >= ChuniConstants.VER_CHUNITHM_NEW + ): + method_fixed += "C3Exp" hash = PBKDF2( method_fixed, - bytes.fromhex(keys[2]), + salt, 128, count=iter_count, hmac_hash_module=SHA1, @@ -137,7 +151,7 @@ class ChuniServlet(BaseServlet): self.hash_table[version][hashed_name] = method_fixed self.logger.debug( - f"Hashed v{version} method {method_fixed} with {bytes.fromhex(keys[2])} to get {hash.hex()}" + f"Hashed v{version} method {method_fixed} with {salt} to get {hashed_name}" ) @classmethod @@ -226,31 +240,39 @@ class ChuniServlet(BaseServlet): 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 + # doing encrypted. The likelihood of false positives is low but # technically not 0 + + if game_code == "SDGS": + crypto_cfg_key = f"{internal_ver}_int" + hash_table_key = f"{internal_ver}_int" + else: + crypto_cfg_key = internal_ver + hash_table_key = internal_ver + if internal_ver < ChuniConstants.VER_CHUNITHM_NEW: endpoint = request.headers.get("User-Agent").split("#")[0] else: - if internal_ver not in self.hash_table: + if hash_table_key not in self.hash_table: self.logger.error( f"v{version} does not support encryption or no keys entered" ) return Response(zlib.compress(b'{"stat": "0"}')) - elif endpoint.lower() not in self.hash_table[internal_ver]: + elif endpoint.lower() not in self.hash_table[hash_table_key]: self.logger.error( f"No hash found for v{version} endpoint {endpoint}" ) return Response(zlib.compress(b'{"stat": "0"}')) - endpoint = self.hash_table[internal_ver][endpoint.lower()] + endpoint = self.hash_table[hash_table_key][endpoint.lower()] try: crypt = AES.new( - bytes.fromhex(self.game_cfg.crypto.keys[internal_ver][0]), + bytes.fromhex(self.game_cfg.crypto.keys[crypto_cfg_key][0]), AES.MODE_CBC, - bytes.fromhex(self.game_cfg.crypto.keys[internal_ver][1]), + bytes.fromhex(self.game_cfg.crypto.keys[crypto_cfg_key][1]), ) req_raw = crypt.decrypt(req_raw) From 784ac544f070c8c620aa7c6523d585d5dd2e6ba3 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sun, 16 Jun 2024 14:42:23 -0400 Subject: [PATCH 37/76] fix event log ordering, paging, add timestamp --- core/data/schema/base.py | 2 +- core/templates/sys/logs.jinja | 40 ++++++++++++++++++++++++----------- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/core/data/schema/base.py b/core/data/schema/base.py index e315964..d74198b 100644 --- a/core/data/schema/base.py +++ b/core/data/schema/base.py @@ -109,7 +109,7 @@ class BaseData: return result.lastrowid async def get_event_log(self, entries: int = 100) -> Optional[List[Row]]: - sql = event_log.select().limit(entries) + sql = event_log.select().order_by(event_log.c.id.desc()).limit(entries) result = await self.execute(sql) if result is None: diff --git a/core/templates/sys/logs.jinja b/core/templates/sys/logs.jinja index 3dfe531..9b7a987 100644 --- a/core/templates/sys/logs.jinja +++ b/core/templates/sys/logs.jinja @@ -6,6 +6,7 @@ Severity + Timestamp System Name User @@ -19,7 +20,7 @@ {% if events is not defined or events|length == 0 %} - No Events + No Events {% endif %} @@ -66,7 +67,11 @@ function update_tbl() { for (var i = 0; i < per_page; i++) { let off = (page * per_page) + i; - if (off >= TBL_DATA.length ) { + if (off >= TBL_DATA.length) { + if (page != 0) { + document.getElementById("btn_next").disabled = true; + document.getElementById("btn_prev").disabled = false; + } break; } @@ -99,56 +104,59 @@ function update_tbl() { row.classList.add("table-primary"); break; } + + var cell_ts = row.insertCell(1); + cell_ts.innerHTML = data.when_logged; - var cell_mod = row.insertCell(1); + var cell_mod = row.insertCell(2); cell_mod.innerHTML = data.system; - var cell_name = row.insertCell(2); + var cell_name = row.insertCell(3); cell_name.innerHTML = data.type; - var cell_usr = row.insertCell(3); + var cell_usr = row.insertCell(4); if (data.user == 'NONE') { cell_usr.innerHTML = "---"; } else { cell_usr.innerHTML = "" + data.user + ""; } - var cell_arcade = row.insertCell(4); + var cell_arcade = row.insertCell(5); if (data.arcade == 'NONE') { cell_arcade.innerHTML = "---"; } else { cell_arcade.innerHTML = "" + data.arcade + ""; } - var cell_machine = row.insertCell(5); + var cell_machine = row.insertCell(6); if (data.arcade == 'NONE') { cell_machine.innerHTML = "---"; } else { cell_machine.innerHTML = "" + data.machine + ""; } - var cell_game = row.insertCell(6); + var cell_game = row.insertCell(7); if (data.game == 'NONE') { cell_game.innerHTML = "---"; } else { cell_game.innerHTML = data.game; } - var cell_version = row.insertCell(7); + var cell_version = row.insertCell(8); if (data.version == 'NONE') { cell_version.innerHTML = "---"; } else { cell_version.innerHTML = data.version; } - var cell_msg = row.insertCell(8); + var cell_msg = row.insertCell(9); if (data.message == '') { cell_msg.innerHTML = "---"; } else { cell_msg.innerHTML = data.message; } - var cell_deets = row.insertCell(9); + var cell_deets = row.insertCell(10); if (data.details == '{}') { cell_deets.innerHTML = "---"; } else { @@ -160,9 +168,11 @@ function update_tbl() { function chg_page(num) { var max_page = TBL_DATA.length / per_page; + console.log(max_page); page = page + num; - if (page > max_page) { + + if (page > max_page && max_page >= 1) { page = max_page; document.getElementById("btn_next").disabled = true; document.getElementById("btn_prev").disabled = false; @@ -172,6 +182,12 @@ function chg_page(num) { document.getElementById("btn_next").disabled = false; document.getElementById("btn_prev").disabled = true; return; + } else if (page == 0) { + document.getElementById("btn_next").disabled = false; + document.getElementById("btn_prev").disabled = true; + } else { + document.getElementById("btn_next").disabled = false; + document.getElementById("btn_prev").disabled = false; } update_tbl(); From 68bf3843ec0515f9c956a62b9319ffe6667de939 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sun, 16 Jun 2024 14:45:41 -0400 Subject: [PATCH 38/76] pokken: fix default title plate id --- titles/pokken/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/titles/pokken/base.py b/titles/pokken/base.py index fe11e99..f864824 100644 --- a/titles/pokken/base.py +++ b/titles/pokken/base.py @@ -226,7 +226,7 @@ class PokkenBase: load_usr.rankmatch_success = profile_dict.get("rankmatch_success", 0) load_usr.beat_num = profile_dict.get("beat_num", 0) load_usr.title_text_id = profile_dict.get("title_text_id", 2) - load_usr.title_plate_id = profile_dict.get("title_plate_id", 1) + load_usr.title_plate_id = profile_dict.get("title_plate_id", 31) load_usr.title_decoration_id = profile_dict.get("title_decoration_id", 1) load_usr.navi_trainer = profile_dict.get("navi_trainer", 0) load_usr.navi_version_id = profile_dict.get("navi_version_id", 0) From 243d40cfd84d14cba581c7dcc65303b1c0e818dc Mon Sep 17 00:00:00 2001 From: beerpsi Date: Tue, 18 Jun 2024 09:39:14 +0700 Subject: [PATCH 39/76] Add changelog and docs --- changelog.md | 4 ++++ docs/game_specific_info.md | 8 +++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 3f8c6ba..a2447e3 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,10 @@ # Changelog Documenting updates to ARTEMiS, to be updated every time the master branch is pushed to. +## 20240616 +### CHUNITHM ++ Support network encryption for Export/International versions + ## 20240530 ### DIVA + Fix reader for when dificulty is not a int diff --git a/docs/game_specific_info.md b/docs/game_specific_info.md index a8e63c5..8e56322 100644 --- a/docs/game_specific_info.md +++ b/docs/game_specific_info.md @@ -87,13 +87,19 @@ Config file is located in `config/chuni.yaml`. | `crypto` | This option is used to enable the TLS Encryption | -**If you would like to use network encryption, the following will be required underneath but key, iv and hash are required:** +If you would like to use network encryption, add the keys to the `keys` section under `crypto`, where the key +is the version ID for Japanese (SDHD) versions and `"{versionID}_int"` for Export (SDGS) versions, and the value +is an array containing `[key, iv, salt, iter_count]` in order. + +`iter_count` is optional for all Japanese (SDHD) versions but may be required for some Export (SDGS) versions. +You will receive an error in the logs if it needs to be specified. ```yaml crypto: encrypted_only: False keys: 13: ["0000000000000000000000000000000000000000000000000000000000000000", "00000000000000000000000000000000", "0000000000000000"] + "13_int": ["0000000000000000000000000000000000000000000000000000000000000000", "00000000000000000000000000000000", "0000000000000000", 42] ``` ### Database upgrade From 766912c51d3a35e9fc1d7ed8606bc39956cfcf65 Mon Sep 17 00:00:00 2001 From: zaphkito Date: Tue, 18 Jun 2024 03:13:32 +0000 Subject: [PATCH 40/76] ongeki: fix base version title work --- titles/ongeki/index.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/titles/ongeki/index.py b/titles/ongeki/index.py index d7f3e0e..f91bf8f 100644 --- a/titles/ongeki/index.py +++ b/titles/ongeki/index.py @@ -216,16 +216,20 @@ class OngekiServlet(BaseServlet): ) return Response(zlib.compress(b'{"stat": "0"}')) - try: - unzip = zlib.decompress(req_raw) + if version < 105: + # O.N.G.E.K.I base don't use zlib + req_data = req_raw + else: + try: + unzip = zlib.decompress(req_raw) + + except zlib.error as e: + self.logger.error( + f"Failed to decompress v{version} {endpoint} request -> {e}" + ) + return Response(zlib.compress(b'{"stat": "0"}')) - except zlib.error as e: - self.logger.error( - f"Failed to decompress v{version} {endpoint} request -> {e}" - ) - return Response(zlib.compress(b'{"stat": "0"}')) - - req_data = json.loads(unzip) + req_data = json.loads(unzip) self.logger.info( f"v{version} {endpoint} request from {client_ip}" @@ -251,9 +255,12 @@ class OngekiServlet(BaseServlet): self.logger.debug(f"Response {resp}") - zipped = zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8")) + resp_raw = json.dumps(resp, ensure_ascii=False).encode("utf-8") + zipped = zlib.compress(resp_raw) if not encrtped or version < 120: + if version < 105: + return Response(resp_raw) return Response(zipped) padded = pad(zipped, 16) From 51f65f9293020a0cefd0520638688ae82de95691 Mon Sep 17 00:00:00 2001 From: zaphkito Date: Tue, 18 Jun 2024 04:15:40 +0000 Subject: [PATCH 41/76] ongeki: I forgot json load --- titles/ongeki/index.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/titles/ongeki/index.py b/titles/ongeki/index.py index f91bf8f..3bd0e15 100644 --- a/titles/ongeki/index.py +++ b/titles/ongeki/index.py @@ -218,7 +218,7 @@ class OngekiServlet(BaseServlet): if version < 105: # O.N.G.E.K.I base don't use zlib - req_data = req_raw + req_data = json.loads(req_raw) else: try: unzip = zlib.decompress(req_raw) From 5378655c520d723ca3977bedbbf0d8f4359372f2 Mon Sep 17 00:00:00 2001 From: beerpsi Date: Thu, 20 Jun 2024 08:11:24 +0700 Subject: [PATCH 42/76] [chunithm] Support LUMINOUS --- changelog.md | 4 + .../b23f985100ba_chunithm_luminous.py | 87 +++++++ docs/game_specific_info.md | 1 + example_config/chuni.yaml | 3 + titles/chuni/const.py | 20 +- titles/chuni/index.py | 21 +- titles/chuni/luminous.py | 244 ++++++++++++++++++ titles/chuni/new.py | 2 + titles/chuni/schema/item.py | 93 +++++++ 9 files changed, 467 insertions(+), 8 deletions(-) create mode 100644 core/data/alembic/versions/b23f985100ba_chunithm_luminous.py create mode 100644 titles/chuni/luminous.py diff --git a/changelog.md b/changelog.md index 3f8c6ba..07f17c8 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,10 @@ # Changelog Documenting updates to ARTEMiS, to be updated every time the master branch is pushed to. +## 20240620 +### CHUNITHM ++ CHUNITHM LUMINOUS support + ## 20240530 ### DIVA + Fix reader for when dificulty is not a int diff --git a/core/data/alembic/versions/b23f985100ba_chunithm_luminous.py b/core/data/alembic/versions/b23f985100ba_chunithm_luminous.py new file mode 100644 index 0000000..dd52974 --- /dev/null +++ b/core/data/alembic/versions/b23f985100ba_chunithm_luminous.py @@ -0,0 +1,87 @@ +"""CHUNITHM LUMINOUS + +Revision ID: b23f985100ba +Revises: 3657efefc5a4 +Create Date: 2024-06-20 08:08:08.759261 + +""" +from alembic import op +from sqlalchemy import Column, Integer, Boolean, UniqueConstraint + + +# revision identifiers, used by Alembic. +revision = 'b23f985100ba' +down_revision = '3657efefc5a4' +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table( + "chuni_profile_net_battle", + Column("id", Integer, primary_key=True, nullable=False), + Column("user", Integer, nullable=False), + Column("isRankUpChallengeFailed", Boolean), + Column("highestBattleRankId", Integer), + Column("battleIconId", Integer), + Column("battleIconNum", Integer), + Column("avatarEffectPoint", Integer), + mysql_charset="utf8mb4", + ) + op.create_foreign_key( + None, + "chuni_profile_net_battle", + "aime_user", + ["user"], + ["id"], + ondelete="cascade", + onupdate="cascade", + ) + + op.create_table( + "chuni_item_cmission", + Column("id", Integer, primary_key=True, nullable=False), + Column("user", Integer, nullable=False), + Column("missionId", Integer, nullable=False), + Column("point", Integer), + UniqueConstraint("user", "missionId", name="chuni_item_cmission_uk"), + mysql_charset="utf8mb4", + ) + op.create_foreign_key( + None, + "chuni_item_cmission", + "aime_user", + ["user"], + ["id"], + ondelete="cascade", + onupdate="cascade", + ) + + op.create_table( + "chuni_item_cmission_progress", + Column("id", Integer, primary_key=True, nullable=False), + Column("user", Integer, nullable=False), + Column("missionId", Integer, nullable=False), + Column("order", Integer), + Column("stage", Integer), + Column("progress", Integer), + UniqueConstraint( + "user", "missionId", "order", name="chuni_item_cmission_progress_uk" + ), + mysql_charset="utf8mb4", + ) + op.create_foreign_key( + None, + "chuni_item_cmission_progress", + "aime_user", + ["user"], + ["id"], + ondelete="cascade", + onupdate="cascade", + ) + + +def downgrade(): + op.drop_table("chuni_profile_net_battle") + op.drop_table("chuni_item_cmission") + op.drop_table("chuni_item_cmission_progress") diff --git a/docs/game_specific_info.md b/docs/game_specific_info.md index a8e63c5..53c076c 100644 --- a/docs/game_specific_info.md +++ b/docs/game_specific_info.md @@ -63,6 +63,7 @@ Games listed below have been tested and confirmed working. | 12 | CHUNITHM NEW PLUS!! | | 13 | CHUNITHM SUN | | 14 | CHUNITHM SUN PLUS | +| 15 | CHUNITHM LUMINOUS | ### Importer diff --git a/example_config/chuni.yaml b/example_config/chuni.yaml index 53da186..4855fa1 100644 --- a/example_config/chuni.yaml +++ b/example_config/chuni.yaml @@ -22,6 +22,9 @@ version: 14: rom: 2.15.00 data: 2.15.00 + 15: + rom: 2.20.00 + data: 2.20.00 crypto: encrypted_only: False diff --git a/titles/chuni/const.py b/titles/chuni/const.py index 3e83378..2b79582 100644 --- a/titles/chuni/const.py +++ b/titles/chuni/const.py @@ -1,3 +1,6 @@ +from enum import Enum + + class ChuniConstants: GAME_CODE = "SDBT" GAME_CODE_NEW = "SDHD" @@ -20,6 +23,7 @@ class ChuniConstants: VER_CHUNITHM_NEW_PLUS = 12 VER_CHUNITHM_SUN = 13 VER_CHUNITHM_SUN_PLUS = 14 + VER_CHUNITHM_LUMINOUS = 15 VERSION_NAMES = [ "CHUNITHM", "CHUNITHM PLUS", @@ -35,9 +39,21 @@ class ChuniConstants: "CHUNITHM NEW!!", "CHUNITHM NEW PLUS!!", "CHUNITHM SUN", - "CHUNITHM SUN PLUS" + "CHUNITHM SUN PLUS", + "CHUNITHM LUMINOUS", ] @classmethod def game_ver_to_string(cls, ver: int): - return cls.VERSION_NAMES[ver] \ No newline at end of file + return cls.VERSION_NAMES[ver] + + +class MapAreaConditionType(Enum): + UNLOCKED = "0" + MAP_AREA_CLEARED = "2" + TROPHY_OBTAINED = "3" + + +class MapAreaConditionLogicalOperator(Enum): + OR = "0" + AND = "1" diff --git a/titles/chuni/index.py b/titles/chuni/index.py index f0f1eac..39dd9ba 100644 --- a/titles/chuni/index.py +++ b/titles/chuni/index.py @@ -1,7 +1,8 @@ from starlette.requests import Request from starlette.routing import Route from starlette.responses import Response -import logging, coloredlogs +import logging +import coloredlogs from logging.handlers import TimedRotatingFileHandler import zlib import yaml @@ -34,6 +35,7 @@ from .new import ChuniNew from .newplus import ChuniNewPlus from .sun import ChuniSun from .sunplus import ChuniSunPlus +from .luminous import ChuniLuminous class ChuniServlet(BaseServlet): def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None: @@ -61,6 +63,7 @@ class ChuniServlet(BaseServlet): ChuniNewPlus, ChuniSun, ChuniSunPlus, + ChuniLuminous, ] self.logger = logging.getLogger("chuni") @@ -103,7 +106,9 @@ class ChuniServlet(BaseServlet): for method in method_list: method_fixed = inflection.camelize(method)[6:-7] # number of iterations was changed to 70 in SUN and then to 36 - if version == ChuniConstants.VER_CHUNITHM_SUN_PLUS: + if version == ChuniConstants.VER_CHUNITHM_LUMINOUS: + iter_count = 8 + elif version == ChuniConstants.VER_CHUNITHM_SUN_PLUS: iter_count = 36 elif version == ChuniConstants.VER_CHUNITHM_SUN: iter_count = 70 @@ -195,8 +200,10 @@ class ChuniServlet(BaseServlet): internal_ver = ChuniConstants.VER_CHUNITHM_NEW_PLUS elif version >= 210 and version < 215: # SUN internal_ver = ChuniConstants.VER_CHUNITHM_SUN - elif version >= 215: # SUN PLUS + elif version >= 215 and version < 220: # SUN PLUS internal_ver = ChuniConstants.VER_CHUNITHM_SUN_PLUS + elif version >= 220: # LUMINOUS + internal_ver = ChuniConstants.VER_CHUNITHM_LUMINOUS elif game_code == "SDGS": # Int if version < 110: # SUPERSTAR / SUPERSTAR PLUS internal_ver = ChuniConstants.VER_CHUNITHM_PARADISE # SUPERSTAR / SUPERSTAR PLUS worked fine with it @@ -206,8 +213,10 @@ class ChuniServlet(BaseServlet): internal_ver = ChuniConstants.VER_CHUNITHM_NEW_PLUS elif version >= 120 and version < 125: # SUN internal_ver = ChuniConstants.VER_CHUNITHM_SUN - elif version >= 125: # SUN PLUS + elif version >= 125 and version < 130: # SUN PLUS internal_ver = ChuniConstants.VER_CHUNITHM_SUN_PLUS + elif version >= 130: # LUMINOUS + internal_ver = ChuniConstants.VER_CHUNITHM_LUMINOUS 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 @@ -295,7 +304,7 @@ class ChuniServlet(BaseServlet): self.logger.error(f"Error handling v{version} method {endpoint} - {e}") return Response(zlib.compress(b'{"stat": "0"}')) - if resp == None: + if resp is None: resp = {"returnCode": 1} self.logger.debug(f"Response {resp}") @@ -313,4 +322,4 @@ class ChuniServlet(BaseServlet): bytes.fromhex(self.game_cfg.crypto.keys[internal_ver][1]), ) - return Response(crypt.encrypt(padded)) \ No newline at end of file + return Response(crypt.encrypt(padded)) diff --git a/titles/chuni/luminous.py b/titles/chuni/luminous.py new file mode 100644 index 0000000..2ced55b --- /dev/null +++ b/titles/chuni/luminous.py @@ -0,0 +1,244 @@ +from typing import Dict + +from core.config import CoreConfig +from titles.chuni.sunplus import ChuniSunPlus +from titles.chuni.const import ChuniConstants, MapAreaConditionLogicalOperator, MapAreaConditionType +from titles.chuni.config import ChuniConfig + + +class ChuniLuminous(ChuniSunPlus): + def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None: + super().__init__(core_cfg, game_cfg) + self.version = ChuniConstants.VER_CHUNITHM_LUMINOUS + + async def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict: + user_data = await super().handle_cm_get_user_preview_api_request(data) + + # Does CARD MAKER 1.35 work this far up? + user_data["lastDataVersion"] = "2.20.00" + return user_data + + async def handle_get_user_c_mission_api_request(self, data: Dict) -> Dict: + user_id = data["userId"] + mission_id = data["missionId"] + + progress_list = [] + point = 0 + + mission_data = await self.data.item.get_cmission(user_id, mission_id) + progress_data = await self.data.item.get_cmission_progress(user_id, mission_id) + + if mission_data and progress_data: + point = mission_data["point"] + + for progress in progress_data: + progress_list.append( + { + "order": progress["order"], + "stage": progress["stage"], + "progress": progress["progress"], + } + ) + + return { + "userId": user_id, + "missionId": mission_id, + "point": point, + "userCMissionProgressList": progress_list, + } + + async def handle_get_user_net_battle_ranking_info_api_request(self, data: Dict) -> Dict: + user_id = data["userId"] + + net_battle = {} + net_battle_data = await self.data.profile.get_net_battle(user_id) + + if net_battle_data: + net_battle = { + "isRankUpChallengeFailed": net_battle_data["isRankUpChallengeFailed"], + "highestBattleRankId": net_battle_data["highestBattleRankId"], + "battleIconId": net_battle_data["battleIconId"], + "battleIconNum": net_battle_data["battleIconNum"], + "avatarEffectPoint": net_battle_data["avatarEffectPoint"], + } + + return { + "userId": user_id, + "userNetBattleData": net_battle, + } + + async def handle_get_game_map_area_condition_api_request(self, data: Dict) -> Dict: + # There is no game data for this, everything is server side. + # TODO: Figure out conditions for 1UM1N0US ep.111 + return { + "length": "7", + "gameMapAreaConditionList": [ + # Secret AREA: MUSIC GAME + { + "mapAreaId": "2206201", # BlythE ULTIMA + "length": "1", + # Obtain the trophy "MISSION in progress", which is only available + # when running the first CHUNITHM mission + "mapAreaConditionList": [ + { + "type": MapAreaConditionType.TROPHY_OBTAINED.value, + "conditionId": "6832", + "logicalOpe": MapAreaConditionLogicalOperator.AND.value, + "startDate": "2023-12-14 07:00:00.0", + "endDate": "2024-01-25 02:00:00.0", + } + ], + }, + { + "mapAreaId": "2206202", # PRIVATE SERVICE ULTIMA + "length": "1", + # Obtain the trophy "MISSION in progress", which is only available + # when running the first CHUNITHM mission + "mapAreaConditionList": [ + { + "type": MapAreaConditionType.TROPHY_OBTAINED.value, + "conditionId": "6832", + "logicalOpe": MapAreaConditionLogicalOperator.AND.value, + "startDate": "2023-12-14 07:00:00.0", + "endDate": "2024-01-25 02:00:00.0", + } + ], + }, + { + "mapAreaId": "2206203", # New York Back Raise + "length": "1", + # SS NightTheater's EXPERT chart and get the title + # "今宵、劇場に映し出される景色とは――――。" + "mapAreaConditionList": [ + { + "type": MapAreaConditionType.TROPHY_OBTAINED.value, + "conditionId": "6833", + "logicalOpe": MapAreaConditionLogicalOperator.AND.value, + "startDate": "2023-12-14 07:00:00.0", + "endDate": "2099-12-31 00:00:00.0", + }, + ], + }, + { + "mapAreaId": "2206204", # Spasmodic + "length": "2", + # - Get 1 miss on Random (any difficulty) and get the title "当たり待ち" + # - Get 1 miss on 花たちに希望を (any difficulty) and get the title "花たちに希望を" + "mapAreaConditionList": [ + { + "type": MapAreaConditionType.TROPHY_OBTAINED.value, + "conditionId": "6834", + "logicalOpe": MapAreaConditionLogicalOperator.AND.value, + "startDate": "2023-12-14 07:00:00.0", + "endDate": "2099-12-31 00:00:00.0", + }, + { + "type": MapAreaConditionType.TROPHY_OBTAINED.value, + "conditionId": "6835", + "logicalOpe": MapAreaConditionLogicalOperator.AND.value, + "startDate": "2023-12-14 07:00:00.0", + "endDate": "2099-12-31 00:00:00.0", + }, + ], + }, + { + "mapAreaId": "2206205", # ΩΩPARTS + "length": "2", + # - S Sage EXPERT to get the title "マターリ進行キボンヌ" + # - Equip this title and play cab-to-cab with another person with this title + # to get "マターリしようよ" + "mapAreaConditionList": [ + { + "type": MapAreaConditionType.TROPHY_OBTAINED.value, + "conditionId": "6836", + "logicalOpe": MapAreaConditionLogicalOperator.AND.value, + "startDate": "2023-12-14 07:00:00.0", + "endDate": "2099-12-31 00:00:00.0", + }, + { + "type": MapAreaConditionType.TROPHY_OBTAINED.value, + "conditionId": "6837", + "logicalOpe": MapAreaConditionLogicalOperator.AND.value, + "startDate": "2023-12-14 07:00:00.0", + "endDate": "2024-01-25 02:00:00.0", + }, + ], + }, + { + "mapAreaId": "2206206", # Blow My Mind + "length": "1", + # SS on CHAOS EXPERT, Hydra EXPERT, Surive EXPERT and Jakarta PROGRESSION EXPERT + # to get the title "Can you hear me?" + "mapAreaConditionList": [ + { + "type": MapAreaConditionType.TROPHY_OBTAINED.value, + "conditionId": "6838", + "logicalOpe": MapAreaConditionLogicalOperator.AND.value, + "startDate": "2023-12-14 07:00:00.0", + "endDate": "2099-12-31 00:00:00.0", + }, + ], + }, + { + "mapAreaId": "2206207", # VALLIS-NERIA + "length": "6", + # Finish the 6 other areas + "mapAreaConditionList": [ + { + "type": MapAreaConditionType.MAP_AREA_CLEARED.value, + "conditionId": str(x), + "logicalOpe": MapAreaConditionLogicalOperator.AND.value, + "startDate": "2023-12-14 07:00:00.0", + "endDate": "2099-12-31 00:00:00.0", + } + for x in range(2206201, 2206207) + ], + }, + # { + # "mapAreaId": "3229301", # Mystic Rainbow of LUMINOUS Area 1 + # "length": "1", + # # Unlocks when any of the mainline LUMINOUS maps are completed? + # "mapAreaConditionList": [ + # # TODO + # ] + # }, + # { + # "mapAreaId": "3229302", # Mystic Rainbow of LUMINOUS Area 2 + # "length": "5", + # # Unlocks when LUMINOUS ep. I is completed + # "mapAreaConditionList": [ + # { + # "type": MapAreaConditionType.MAP_AREA_CLEARED.value, + # "conditionId": str(x), + # "logicalOpe": MapAreaConditionLogicalOperator.AND.value, + # "startDate": "2023-12-14 07:00:00.0", + # "endDate": "2099-12-31 00:00:00.0", + # } + # for x in range(3220101, 3220106) + # ] + # }, + # { + # "mapAreaId": "3229303", # Mystic Rainbow of LUMINOUS Area 3 + # "length": "5", + # # Unlocks when LUMINOUS ep. II is completed + # "mapAreaConditionList": [ + # { + # "type": MapAreaConditionType.MAP_AREA_CLEARED.value, + # "conditionId": str(x), + # "logicalOpe": MapAreaConditionLogicalOperator.AND.value, + # "startDate": "2023-12-14 07:00:00.0", + # "endDate": "2099-12-31 00:00:00.0", + # } + # for x in range(3220201, 3220206) + # ] + # }, + # { + # "mapAreaId": "3229304", # Mystic Rainbow of LUMINOUS Area 4 + # "length": "5", + # # Unlocks when LUMINOUS ep. III is completed + # "mapAreaConditionList": [ + + # ] + # } + ], + } diff --git a/titles/chuni/new.py b/titles/chuni/new.py index 9709f00..2275a6e 100644 --- a/titles/chuni/new.py +++ b/titles/chuni/new.py @@ -32,6 +32,8 @@ class ChuniNew(ChuniBase): return "210" if self.version == ChuniConstants.VER_CHUNITHM_SUN_PLUS: return "215" + if self.version == ChuniConstants.VER_CHUNITHM_LUMINOUS: + return "220" async def handle_get_game_setting_api_request(self, data: Dict) -> Dict: # use UTC time and convert it to JST time by adding +9 diff --git a/titles/chuni/schema/item.py b/titles/chuni/schema/item.py index 5077e14..2f386ef 100644 --- a/titles/chuni/schema/item.py +++ b/titles/chuni/schema/item.py @@ -243,6 +243,36 @@ matching = Table( mysql_charset="utf8mb4", ) +cmission = Table( + "chuni_item_cmission", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column( + "user", + ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), + nullable=False, + ), + Column("missionId", Integer, nullable=False), + Column("point", Integer), + UniqueConstraint("user", "missionId", name="chuni_item_cmission_uk"), + mysql_charset="utf8mb4", +) + +cmission_progress = Table( + "chuni_item_cmission_progress", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade"), nullable=False), + Column("missionId", Integer, nullable=False), + Column("order", Integer), + Column("stage", Integer), + Column("progress", Integer), + UniqueConstraint( + "user", "missionId", "order", name="chuni_item_cmission_progress_uk" + ), + mysql_charset="utf8mb4", +) + class ChuniItemData(BaseData): async def get_oldest_free_matching(self, version: int) -> Optional[Row]: @@ -594,3 +624,66 @@ class ChuniItemData(BaseData): ) return None return result.lastrowid + + async def put_cmission_progress( + self, user_id: int, mission_id: int, progress_data: Dict + ) -> Optional[int]: + progress_data["user"] = user_id + progress_data["missionId"] = mission_id + + sql = insert(cmission_progress).values(**progress_data) + conflict = sql.on_duplicate_key_update(**progress_data) + result = await self.execute(conflict) + + if result is None: + return None + + return result.lastrowid + + async def get_cmission_progress( + self, user_id: int, mission_id: int + ) -> Optional[List[Row]]: + sql = cmission_progress.select( + and_( + cmission_progress.c.user == user_id, + cmission_progress.c.missionId == mission_id, + ) + ).order_by(cmission_progress.c.order.asc()) + result = await self.execute(sql) + + if result is None: + return None + + return result.fetchall() + + async def get_cmission(self, user_id: int, mission_id: int) -> Optional[Row]: + sql = cmission.select( + and_(cmission.c.user == user_id, cmission.c.missionId == mission_id) + ) + result = await self.execute(sql) + + if result is None: + return None + + return result.fetchone() + + async def put_cmission(self, user_id: int, mission_data: Dict) -> Optional[int]: + mission_data["user"] = user_id + + sql = insert(cmission).values(**mission_data) + conflict = sql.on_duplicate_key_update(**mission_data) + result = await self.execute(conflict) + + if result is None: + return None + + return result.lastrowid + + async def get_cmissions(self, user_id: int) -> Optional[List[Row]]: + sql = cmission.select(cmission.c.user == user_id) + result = await self.execute(sql) + + if result is None: + return None + + return result.fetchall() From a1d54efbace85066234e2ec90620947e0d086cad Mon Sep 17 00:00:00 2001 From: beerpsi Date: Thu, 20 Jun 2024 20:28:52 +0700 Subject: [PATCH 43/76] add upserts --- titles/chuni/base.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/titles/chuni/base.py b/titles/chuni/base.py index 9e8a634..2a662d7 100644 --- a/titles/chuni/base.py +++ b/titles/chuni/base.py @@ -941,6 +941,31 @@ class ChuniBase: rating_type, upsert[rating_type], ) + + # added in LUMINOUS + if "userCMissionList" in upsert: + for cmission in upsert["userCMissionList"]: + mission_id = cmission["missionId"] + + await self.data.item.put_cmission( + user_id, + { + "missionId": mission_id, + "point": cmission["point"], + }, + ) + + for progress in cmission["userCMissionProgressList"]: + await self.data.item.put_cmission_progress(user_id, mission_id, progress) + + if "userNetBattleData" in upsert: + net_battle = upsert["userNetBattleData"][0] + + # fix the boolean + net_battle["isRankUpChallengeFailed"] = ( + False if net_battle["isRankUpChallengeFailed"] == "false" else True + ) + await self.data.profile.put_net_battle(user_id, net_battle) return {"returnCode": "1"} @@ -969,4 +994,4 @@ class ChuniBase: return { "userId": data["userId"], "userNetBattleData": {"recentNBSelectMusicList": []}, - } \ No newline at end of file + } From a5b47e20959b6394972061d2989bb7fb22939b60 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Thu, 20 Jun 2024 10:59:26 -0400 Subject: [PATCH 44/76] wacca: fix vs song updates --- titles/wacca/base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/titles/wacca/base.py b/titles/wacca/base.py index 8a37c2b..58b1ba9 100644 --- a/titles/wacca/base.py +++ b/titles/wacca/base.py @@ -833,14 +833,14 @@ class WaccaBase: # TODO: Coop and vs data async def handle_user_music_updateCoop_request(self, data: Dict) -> Dict: coop_info = data["params"][4] - return self.handle_user_music_update_request(data) + return await self.handle_user_music_update_request(data) async def handle_user_music_updateVersus_request(self, data: Dict) -> Dict: vs_info = data["params"][4] - return self.handle_user_music_update_request(data) + return await self.handle_user_music_update_request(data) async def handle_user_music_updateTrial_request(self, data: Dict) -> Dict: - return self.handle_user_music_update_request(data) + return await self.handle_user_music_update_request(data) async def handle_user_mission_update_request(self, data: Dict) -> Dict: req = UserMissionUpdateRequest(data) From 7aa3cf82f1b6600e0fd3e72ab979e869af05bf26 Mon Sep 17 00:00:00 2001 From: ThatzOkay Date: Sat, 22 Jun 2024 02:03:02 +0000 Subject: [PATCH 45/76] Diva front end (#150) Basic diva frontend implemented. Changing name and level string working. And some basic playlog page which needs more data Co-authored-by: = <=> Reviewed-on: https://gitea.tendokyu.moe/Hay1tsme/artemis/pulls/150 Co-authored-by: ThatzOkay Co-committed-by: ThatzOkay --- changelog.md | 4 + titles/diva/__init__.py | 2 + titles/diva/frontend.py | 182 +++++++++++++++++++++ titles/diva/schema/profile.py | 6 +- titles/diva/schema/score.py | 20 +++ titles/diva/templates/css/diva_style.css | 195 +++++++++++++++++++++++ titles/diva/templates/diva_header.jinja | 17 ++ titles/diva/templates/diva_index.jinja | 111 +++++++++++++ titles/diva/templates/diva_playlog.jinja | 169 ++++++++++++++++++++ 9 files changed, 704 insertions(+), 2 deletions(-) create mode 100644 titles/diva/frontend.py create mode 100644 titles/diva/templates/css/diva_style.css create mode 100644 titles/diva/templates/diva_header.jinja create mode 100644 titles/diva/templates/diva_index.jinja create mode 100644 titles/diva/templates/diva_playlog.jinja diff --git a/changelog.md b/changelog.md index 07f17c8..8148a9d 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,10 @@ Documenting updates to ARTEMiS, to be updated every time the master branch is pu ### CHUNITHM + CHUNITHM LUMINOUS support +## 20240616 +### DIVA ++ Working frontend with name and level strings edit and playlog + ## 20240530 ### DIVA + Fix reader for when dificulty is not a int diff --git a/titles/diva/__init__.py b/titles/diva/__init__.py index d298ba2..7bfa2cc 100644 --- a/titles/diva/__init__.py +++ b/titles/diva/__init__.py @@ -2,8 +2,10 @@ from titles.diva.index import DivaServlet from titles.diva.const import DivaConstants from titles.diva.database import DivaData from titles.diva.read import DivaReader +from .frontend import DivaFrontend index = DivaServlet database = DivaData reader = DivaReader +frontend = DivaFrontend game_codes = [DivaConstants.GAME_CODE] diff --git a/titles/diva/frontend.py b/titles/diva/frontend.py new file mode 100644 index 0000000..cc5c332 --- /dev/null +++ b/titles/diva/frontend.py @@ -0,0 +1,182 @@ +from typing import List +from starlette.routing import Route, Mount +from starlette.requests import Request +from starlette.responses import Response, RedirectResponse +from os import path +import yaml +import jinja2 + +from core.frontend import FE_Base, UserSession +from core.config import CoreConfig +from .database import DivaData +from .config import DivaConfig +from .const import DivaConstants + +class DivaFrontend(FE_Base): + def __init__( + self, cfg: CoreConfig, environment: jinja2.Environment, cfg_dir: str + ) -> None: + super().__init__(cfg, environment) + self.data = DivaData(cfg) + self.game_cfg = DivaConfig() + if path.exists(f"{cfg_dir}/{DivaConstants.CONFIG_NAME}"): + self.game_cfg.update( + yaml.safe_load(open(f"{cfg_dir}/{DivaConstants.CONFIG_NAME}")) + ) + self.nav_name = "diva" + + def get_routes(self) -> List[Route]: + return [ + Route("/", self.render_GET, methods=['GET']), + Mount("/playlog", routes=[ + Route("/", self.render_GET_playlog, methods=['GET']), + Route("/{index}", self.render_GET_playlog, methods=['GET']), + ]), + Route("/update.name", self.update_name, methods=['POST']), + Route("/update.lv", self.update_lv, methods=['POST']), + ] + + async def render_GET(self, request: Request) -> bytes: + template = self.environment.get_template( + "titles/diva/templates/diva_index.jinja" + ) + usr_sesh = self.validate_session(request) + if not usr_sesh: + usr_sesh = UserSession() + + if usr_sesh.user_id > 0: + profile = await self.data.profile.get_profile(usr_sesh.user_id, 1) + + resp = Response(template.render( + title=f"{self.core_config.server.name} | {self.nav_name}", + game_list=self.environment.globals["game_list"], + sesh=vars(usr_sesh), + user_id=usr_sesh.user_id, + profile=profile + ), media_type="text/html; charset=utf-8") + return resp + else: + return RedirectResponse("/gate") + + async def render_GET_playlog(self, request: Request) -> bytes: + template = self.environment.get_template( + "titles/diva/templates/diva_playlog.jinja" + ) + usr_sesh = self.validate_session(request) + if not usr_sesh: + usr_sesh = UserSession() + + if usr_sesh.user_id > 0: + path_index = request.path_params.get("index") + if not path_index or int(path_index) < 1: + index = 0 + else: + index = int(path_index) - 1 # 0 and 1 are 1st page + user_id = usr_sesh.user_id + playlog_count = await self.data.score.get_user_playlogs_count(user_id) + if playlog_count < index * 20 : + return Response(template.render( + title=f"{self.core_config.server.name} | {self.nav_name}", + game_list=self.environment.globals["game_list"], + sesh=vars(usr_sesh), + score_count=0 + ), media_type="text/html; charset=utf-8") + playlog = await self.data.score.get_playlogs(user_id, index, 20) #Maybe change to the playlog instead of direct scores + playlog_with_title = [] + for record in playlog: + song = await self.data.static.get_music_chart(record[2], record[3], record[4]) + if song: + title = song.title + vocaloid_arranger = song.vocaloid_arranger + else: + title = "Unknown" + vocaloid_arranger = "Unknown" + playlog_with_title.append({ + "raw": record, + "title": title, + "vocaloid_arranger": vocaloid_arranger + }) + return Response(template.render( + title=f"{self.core_config.server.name} | {self.nav_name}", + game_list=self.environment.globals["game_list"], + sesh=vars(usr_sesh), + user_id=usr_sesh.user_id, + playlog=playlog_with_title, + playlog_count=playlog_count + ), media_type="text/html; charset=utf-8") + else: + return RedirectResponse("/gate/", 300) + + async def update_name(self, request: Request) -> Response: + usr_sesh = self.validate_session(request) + if not usr_sesh: + return RedirectResponse("/gate") + + form_data = await request.form() + new_name: str = form_data.get("new_name") + new_name_full = "" + + if not new_name: + return RedirectResponse("/gate/?e=4", 303) + + if len(new_name) > 8: + return RedirectResponse("/gate/?e=8", 303) + + for x in new_name: # FIXME: This will let some invalid characters through atm + o = ord(x) + try: + if o == 0x20: + new_name_full += chr(0x3000) + elif o < 0x7F and o > 0x20: + new_name_full += chr(o + 0xFEE0) + elif o <= 0x7F: + self.logger.warn(f"Invalid ascii character {o:02X}") + return RedirectResponse("/gate/?e=4", 303) + else: + new_name_full += x + + except Exception as e: + self.logger.error(f"Something went wrong parsing character {o:04X} - {e}") + return RedirectResponse("/gate/?e=4", 303) + + if not await self.data.profile.update_profile(usr_sesh.user_id, player_name=new_name_full): + return RedirectResponse("/gate/?e=999", 303) + + return RedirectResponse("/game/diva", 303) + + async def update_lv(self, request: Request) -> Response: + usr_sesh = self.validate_session(request) + if not usr_sesh: + return RedirectResponse("/gate") + + form_data = await request.form() + new_lv: str = form_data.get("new_lv") + new_lv_full = "" + + if not new_lv: + return RedirectResponse("/gate/?e=4", 303) + + if len(new_lv) > 8: + return RedirectResponse("/gate/?e=8", 303) + + for x in new_lv: # FIXME: This will let some invalid characters through atm + o = ord(x) + try: + if o == 0x20: + new_lv_full += chr(0x3000) + elif o < 0x7F and o > 0x20: + new_lv_full += chr(o + 0xFEE0) + elif o <= 0x7F: + self.logger.warn(f"Invalid ascii character {o:02X}") + return RedirectResponse("/gate/?e=4", 303) + else: + new_lv_full += x + + except Exception as e: + self.logger.error(f"Something went wrong parsing character {o:04X} - {e}") + return RedirectResponse("/gate/?e=4", 303) + + if not await self.data.profile.update_profile(usr_sesh.user_id, lv_str=new_lv_full): + return RedirectResponse("/gate/?e=999", 303) + + return RedirectResponse("/game/diva", 303) diff --git a/titles/diva/schema/profile.py b/titles/diva/schema/profile.py index f3d00ae..10d3b51 100644 --- a/titles/diva/schema/profile.py +++ b/titles/diva/schema/profile.py @@ -90,7 +90,7 @@ class DivaProfileData(BaseData): return None return result.lastrowid - async def update_profile(self, aime_id: int, **profile_args) -> None: + async def update_profile(self, aime_id: int, **profile_args) -> bool: """ Given an aime_id update the profile corresponding to the arguments which are the diva_profile Columns @@ -102,7 +102,9 @@ class DivaProfileData(BaseData): self.logger.error( f"update_profile: failed to update profile! profile: {aime_id}" ) - return None + return False + + return True async def get_profile(self, aime_id: int, version: int) -> Optional[List[Dict]]: """ diff --git a/titles/diva/schema/score.py b/titles/diva/schema/score.py index e802a41..ce89f74 100644 --- a/titles/diva/schema/score.py +++ b/titles/diva/schema/score.py @@ -239,3 +239,23 @@ class DivaScoreData(BaseData): if result is None: return None return result.fetchall() + + async def get_playlogs(self, aime_id: int, idx: int = 0, limit: int = 0) -> Optional[Row]: + sql = select(playlog).where(playlog.c.user == aime_id).order_by(playlog.c.date_scored.desc()) + + if limit: + sql = sql.limit(limit) + if idx: + sql = sql.offset(idx) + + result = await self.execute(sql) + if result: + return result.fetchall() + + async def get_user_playlogs_count(self, aime_id: int) -> Optional[int]: + sql = select(func.count()).where(playlog.c.user == aime_id) + result = await self.execute(sql) + if result is None: + self.logger.warning(f"aimu_id {aime_id} has no scores ") + return None + return result.scalar() diff --git a/titles/diva/templates/css/diva_style.css b/titles/diva/templates/css/diva_style.css new file mode 100644 index 0000000..672db0f --- /dev/null +++ b/titles/diva/templates/css/diva_style.css @@ -0,0 +1,195 @@ +.diva-header { + text-align: center; +} + +ul.diva-navi { + list-style-type: none; + padding: 0; + overflow: hidden; + background-color: #333; + text-align: center; + display: inline-block; +} + +ul.diva-navi li { + display: inline-block; +} + +ul.diva-navi li a { + display: block; + color: white; + text-align: center; + padding: 14px 16px; + text-decoration: none; +} + +ul.diva-navi li a:hover:not(.active) { + background-color: #111; +} + +ul.diva-navi li a.active { + background-color: #4CAF50; +} + +ul.diva-navi li.right { + float: right; +} + +@media screen and (max-width: 600px) { + + ul.diva-navi li.right, + ul.diva-navi li { + float: none; + display: block; + text-align: center; + } +} + +table { + border-collapse: collapse; + border-spacing: 0; + border-collapse: separate; + overflow: hidden; + background-color: #555555; + +} + +th, td { + text-align: left; + border: none; + +} + +th { + color: white; +} + +.table-rowdistinct tr:nth-child(even) { + background-color: #303030; +} + +.table-rowdistinct tr:nth-child(odd) { + background-color: #555555; +} + +caption { + text-align: center; + color: white; + font-size: 18px; + font-weight: bold; +} + +.table-large { + margin: 16px; +} + +.table-large th, +.table-large td { + padding: 8px; +} + +.table-small { + width: 100%; + margin: 4px; +} + +.table-small th, +.table-small td { + padding: 2px; +} + +.bg-card { + background-color: #555555; +} + +.card-hover { + transition: all 0.2s ease-in-out; +} + +.card-hover:hover { + transform: scale(1.02); +} + +.basic { + color: #28a745; + font-weight: bold; +} + +.hard { + color: #ffc107; + + font-weight: bold; +} + +.expert { + color: #dc3545; + font-weight: bold; +} + +.master { + color: #dd09e8; + font-weight: bold; +} + +.ultimate { + color: #000000; + font-weight: bold; +} + +.score { + color: #ffffff; + font-weight: bold; +} + +.rainbow { + background: linear-gradient(to right, red, yellow, lime, aqua, blue, fuchsia) 0 / 5em; + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + font-weight: bold; +} + +.platinum { + color: #FFFF00; + font-weight: bold; +} + +.gold { + color: #FFFF00; + font-weight: bold; +} + +.scrolling-text { + overflow: hidden; +} + +.scrolling-text p { + white-space: nowrap; + display: inline-block; + +} + +.scrolling-text h6 { + white-space: nowrap; + display: inline-block; + +} + +.scrolling-text h5 { + white-space: nowrap; + display: inline-block; + +} + +.scrolling { + animation: scroll 10s linear infinite; +} + +@keyframes scroll { + 0% { + transform: translateX(100%); + } + 100% { + transform: translateX(-100%); + } +} \ No newline at end of file diff --git a/titles/diva/templates/diva_header.jinja b/titles/diva/templates/diva_header.jinja new file mode 100644 index 0000000..b92379a --- /dev/null +++ b/titles/diva/templates/diva_header.jinja @@ -0,0 +1,17 @@ +
+

diva

+ +
+ \ No newline at end of file diff --git a/titles/diva/templates/diva_index.jinja b/titles/diva/templates/diva_index.jinja new file mode 100644 index 0000000..c2f0888 --- /dev/null +++ b/titles/diva/templates/diva_index.jinja @@ -0,0 +1,111 @@ +{% extends "core/templates/index.jinja" %} +{% block content %} + +
+ {% include 'titles/diva/templates/diva_header.jinja' %} + {% if profile is defined and profile is not none and profile|length > 0 %} +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OVERVIEW
Player name:{{ profile[3] }} + + Level string:{{ profile[4] }} + +
Lvl:{{ profile[5] }}
Lvl points:{{ profile[6] }}
Vocaloid points:{{ profile[7] }}
+
+
+
+ {% if error is defined %} + {% include "core/templates/widgets/err_banner.jinja" %} + {% endif %} + {% elif sesh is defined and sesh is not none and sesh.user_id > 0 %} + No profile information found for this account. + {% else %} + Login to view profile information. + {% endif %} +
+ + +{% endblock %} \ No newline at end of file diff --git a/titles/diva/templates/diva_playlog.jinja b/titles/diva/templates/diva_playlog.jinja new file mode 100644 index 0000000..c5a5618 --- /dev/null +++ b/titles/diva/templates/diva_playlog.jinja @@ -0,0 +1,169 @@ +{% extends "core/templates/index.jinja" %} +{% block content %} + +
+ {% include 'titles/diva/templates/diva_header.jinja' %} + {% if playlog is defined and playlog is not none %} +
+

Score counts: {{ playlog_count }}

+ {% set difficultyName = ['easy', 'normal', 'hard', 'extreme', 'extra extreme'] %} + {% set clearState = ['MISSxTAKE', 'STANDARD', 'GREAT', 'EXELLENT', 'PERFECT'] %} + {% for record in playlog %} +
+
+
+
+
+
{{ record.title }}
+
+
{{ record.vocaloid_arranger }}
+
+
+
{{record.raw.date_scored}}
+
+
+
+
+

{{ record.raw.score }}

+

{{ record.raw.atn_pnt / 100 }}%

+
{{ difficultyName[record.raw.difficulty] }}
+
+
+ + + + + + + + + + + + + + + + + + + + + +
COOL{{ record.raw.cool }}
FINE{{ record.raw.fine }}
SAFE{{ record.raw.safe }}
SAD{{ record.raw.sad }}
WORST{{ record.raw.worst }}
+
+
+
{{ record.raw.max_combo }}
+ {% if record.raw.clr_kind == -1 %} +
{{ clearState[0] }}
+ {% elif record.raw.clr_kind == 2 %} +
{{ clearState[1] }}
+ {% elif record.raw.clr_kind == 3 %} +
{{ clearState[2] }}
+ {% elif record.raw.clr_kind == 4 %} +
{{ clearState[3] }}
+ {% elif record.raw.clr_kind == 5 %} +
{{ clearState[4] }}
+ {% endif %} + {% if record.raw.clr_kind == -1 %} +
NOT CLEAR
+ {% else %} +
CLEAR
+ {% endif %} +
+
+
+
+
+ {% endfor %} +
+ {% set playlog_pages = playlog_count // 20 + 1 %} + {% elif sesh is defined and sesh is not none and sesh.user_id > 0 %} + No Score information found for this account. + {% else %} + Login to view profile information. + {% endif %} +
+ + + +{% endblock content %} \ No newline at end of file From bf5c959324de7c26b735a896c53b3e03a582d04e Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Fri, 21 Jun 2024 22:19:57 -0400 Subject: [PATCH 46/76] chuni: fix reader issue caused by encoding weirdness (#55) --- titles/chuni/read.py | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/titles/chuni/read.py b/titles/chuni/read.py index 5082f7a..db7435c 100644 --- a/titles/chuni/read.py +++ b/titles/chuni/read.py @@ -48,9 +48,8 @@ class ChuniReader(BaseReader): for root, dirs, files in walk(f"{root_dir}loginBonusPreset"): for dir in dirs: if path.exists(f"{root}/{dir}/LoginBonusPreset.xml"): - with open(f"{root}/{dir}/LoginBonusPreset.xml", "rb") as fp: - bytedata = fp.read() - strdata = bytedata.decode("UTF-8") + with open(f"{root}/{dir}/LoginBonusPreset.xml", "r", encoding="utf-8") as fp: + strdata = fp.read() xml_root = ET.fromstring(strdata) for name in xml_root.findall("name"): @@ -121,9 +120,8 @@ class ChuniReader(BaseReader): 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") + with open(f"{root}/{dir}/Event.xml", "r", encoding="utf-8") as fp: + strdata = fp.read() xml_root = ET.fromstring(strdata) for name in xml_root.findall("name"): @@ -144,9 +142,8 @@ class ChuniReader(BaseReader): for root, dirs, files in walk(music_dir): for dir in dirs: if path.exists(f"{root}/{dir}/Music.xml"): - with open(f"{root}/{dir}/Music.xml", "rb") as fp: - bytedata = fp.read() - strdata = bytedata.decode("UTF-8") + with open(f"{root}/{dir}/Music.xml", "r", encoding='utf-8') as fp: + strdata = fp.read() xml_root = ET.fromstring(strdata) for name in xml_root.findall("name"): @@ -210,9 +207,8 @@ class ChuniReader(BaseReader): 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") + with open(f"{root}/{dir}/ChargeItem.xml", "r", encoding='utf-8') as fp: + strdata = fp.read() xml_root = ET.fromstring(strdata) for name in xml_root.findall("name"): @@ -240,9 +236,8 @@ class ChuniReader(BaseReader): for root, dirs, files in walk(avatar_dir): for dir in dirs: if path.exists(f"{root}/{dir}/AvatarAccessory.xml"): - with open(f"{root}/{dir}/AvatarAccessory.xml", "rb") as fp: - bytedata = fp.read() - strdata = bytedata.decode("UTF-8") + with open(f"{root}/{dir}/AvatarAccessory.xml", "r", encoding='utf-8') as fp: + strdata = fp.read() xml_root = ET.fromstring(strdata) for name in xml_root.findall("name"): From ca9ccbe8a37b6eef5c78bfabae7ae69d5faff841 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Fri, 21 Jun 2024 23:00:35 -0400 Subject: [PATCH 47/76] chuni: add missing net battle funcs --- .../1e150d16ab6b_chuni_add_net_battle_uk.py | 27 ++++++++++++ titles/chuni/schema/item.py | 2 +- titles/chuni/schema/profile.py | 42 ++++++++++++++++++- 3 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 core/data/alembic/versions/1e150d16ab6b_chuni_add_net_battle_uk.py diff --git a/core/data/alembic/versions/1e150d16ab6b_chuni_add_net_battle_uk.py b/core/data/alembic/versions/1e150d16ab6b_chuni_add_net_battle_uk.py new file mode 100644 index 0000000..5fcd5a8 --- /dev/null +++ b/core/data/alembic/versions/1e150d16ab6b_chuni_add_net_battle_uk.py @@ -0,0 +1,27 @@ +"""chuni_add_net_battle_uk + +Revision ID: 1e150d16ab6b +Revises: b23f985100ba +Create Date: 2024-06-21 22:57:18.418488 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = '1e150d16ab6b' +down_revision = 'b23f985100ba' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_unique_constraint(None, 'chuni_profile_net_battle', ['user']) + # ### end Alembic commands ### + + +def downgrade(): + op.drop_constraint(None, 'chuni_profile_net_battle', type_='unique') + # ### end Alembic commands ### diff --git a/titles/chuni/schema/item.py b/titles/chuni/schema/item.py index 2f386ef..30db4b8 100644 --- a/titles/chuni/schema/item.py +++ b/titles/chuni/schema/item.py @@ -262,7 +262,7 @@ cmission_progress = Table( "chuni_item_cmission_progress", metadata, Column("id", Integer, primary_key=True, nullable=False), - Column("user", ForeignKey("aime_user.id", ondelete="cascade"), nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), Column("missionId", Integer, nullable=False), Column("order", Integer), Column("stage", Integer), diff --git a/titles/chuni/schema/profile.py b/titles/chuni/schema/profile.py index 2f8bce3..7ee0164 100644 --- a/titles/chuni/schema/profile.py +++ b/titles/chuni/schema/profile.py @@ -412,6 +412,18 @@ rating = Table( mysql_charset="utf8mb4", ) +net_battle = Table( + "chuni_profile_net_battle", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", Integer, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False, unique=True), + Column("isRankUpChallengeFailed", Boolean), + Column("highestBattleRankId", Integer), + Column("battleIconId", Integer), + Column("battleIconNum", Integer), + Column("avatarEffectPoint", Integer), + mysql_charset="utf8mb4", +) class ChuniProfileData(BaseData): async def update_name(self, user_id: int, new_name: str) -> bool: @@ -779,4 +791,32 @@ class ChuniProfileData(BaseData): else: versions_raw = result.fetchall() versions = [row[0] for row in versions_raw] - return sorted(versions, reverse=True) \ No newline at end of file + return sorted(versions, reverse=True) + + async def put_net_battle(self, user_id: int, net_battle_data: Dict) -> Optional[int]: + sql = insert(net_battle).values( + user=user_id, + isRankUpChallengeFailed=net_battle_data['isRankUpChallengeFailed'], + highestBattleRankId=net_battle_data['highestBattleRankId'], + battleIconId=net_battle_data['battleIconId'], + battleIconNum=net_battle_data['battleIconNum'], + avatarEffectPoint=net_battle_data['avatarEffectPoint'], + ) + + conflict = sql.on_duplicate_key_update( + isRankUpChallengeFailed=net_battle_data['isRankUpChallengeFailed'], + highestBattleRankId=net_battle_data['highestBattleRankId'], + battleIconId=net_battle_data['battleIconId'], + battleIconNum=net_battle_data['battleIconNum'], + avatarEffectPoint=net_battle_data['avatarEffectPoint'], + ) + + result = await self.execute(conflict) + if result: + return result.inserted_primary_key['id'] + self.logger.error(f"Failed to put net battle data for user {user_id}") + + async def get_net_battle(self, user_id: int, net_battle_data: Dict) -> Optional[Row]: + result = await self.execute(net_battle.select(net_battle.c.user == user_id)) + if result: + return result.fetchone() From 3979a020a6d39a0856b9822c8bfabfd5af4f7a99 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sat, 22 Jun 2024 00:47:12 -0400 Subject: [PATCH 48/76] add idm and chip_id fields to card table --- core/adb_handlers/felica.py | 9 +-- core/aimedb.py | 61 ++++++++++++++++--- .../48f4acc43a7e_card_add_idm_chip_id.py | 50 +++++++++++++++ core/data/schema/card.py | 34 ++++++++--- titles/pokken/base.py | 4 ++ titles/sao/base.py | 4 ++ 6 files changed, 140 insertions(+), 22 deletions(-) create mode 100644 core/data/alembic/versions/48f4acc43a7e_card_add_idm_chip_id.py diff --git a/core/adb_handlers/felica.py b/core/adb_handlers/felica.py index 479e84d..22c9f05 100644 --- a/core/adb_handlers/felica.py +++ b/core/adb_handlers/felica.py @@ -10,13 +10,14 @@ class ADBFelicaLookupRequest(ADBBaseRequest): self.pmm = hex(pmm)[2:].upper() class ADBFelicaLookupResponse(ADBBaseResponse): - def __init__(self, access_code: str = None, game_id: str = "SXXX", store_id: int = 1, keychip_id: str = "A69E01A8888", code: int = 0x03, length: int = 0x30, status: int = 1) -> None: + def __init__(self, access_code: str = None, idx: int = 0, game_id: str = "SXXX", store_id: int = 1, keychip_id: str = "A69E01A8888", code: int = 0x03, length: int = 0x30, status: int = 1) -> None: super().__init__(code, length, status, game_id, store_id, keychip_id) self.access_code = access_code if access_code is not None else "00000000000000000000" + self.idx = idx @classmethod - def from_req(cls, req: ADBHeader, access_code: str = None) -> "ADBFelicaLookupResponse": - c = cls(access_code, req.game_id, req.store_id, req.keychip_id) + def from_req(cls, req: ADBHeader, access_code: str = None, idx: int = 0) -> "ADBFelicaLookupResponse": + c = cls(access_code, idx, req.game_id, req.store_id, req.keychip_id) c.head.protocol_ver = req.protocol_ver return c @@ -26,7 +27,7 @@ class ADBFelicaLookupResponse(ADBBaseResponse): "access_code" / Int8ub[10], Padding(2) ).build(dict( - felica_idx = 0, + felica_idx = self.idx, access_code = bytes.fromhex(self.access_code) )) diff --git a/core/aimedb.py b/core/aimedb.py index 08a1b65..c6887d3 100644 --- a/core/aimedb.py +++ b/core/aimedb.py @@ -194,6 +194,9 @@ class AimedbServlette(): if user_id and user_id > 0: await self.data.card.update_card_last_login(req.access_code) + if req.access_code.startswith("010") or req.access_code.startswith("3"): + await self.data.card.set_chip_id_by_access_code(req.access_code, req.serial_number) + self.logger.info(f"Attempt to set chip id to {req.serial_number} for access code {req.access_code}") return ret async def handle_lookup_ex(self, data: bytes, resp_code: int) -> ADBBaseResponse: @@ -229,15 +232,24 @@ class AimedbServlette(): async def handle_felica_lookup(self, data: bytes, resp_code: int) -> bytes: """ - On official, I think a card has to be registered for this to actually work, but - I'm making the executive decision to not implement that and just kick back our - faux generated access code. The real felica IDm -> access code conversion is done - on the ADB server, which we do not and will not ever have access to. Because we can - assure that all IDms will be unique, this basic 0-padded hex -> int conversion will - be fine. + On official, the IDm is used as a key to look up the stored access code in a large + database. We do not have access to that database so we have to make due with what we got. + Interestingly, namco games are able to read S_PAD0 and send the server the correct access + code, but aimedb doesn't. Until somebody either enters the correct code manually, or scans + on a game that reads it correctly from the card, this will have to do. It's the same conversion + used on the big boy networks. """ req = ADBFelicaLookupRequest(data) - ac = self.data.card.to_access_code(req.idm) + card = await self.data.card.get_card_by_idm(req.idm) + if not card: + ac = self.data.card.to_access_code(req.idm) + test = await self.data.card.get_card_by_access_code(ac) + if test: + await self.data.card.set_idm_by_access_code(ac, req.idm) + + else: + ac = card['access_code'] + self.logger.info( f"idm {req.idm} ipm {req.pmm} -> access_code {ac}" ) @@ -245,7 +257,8 @@ class AimedbServlette(): async def handle_felica_register(self, data: bytes, resp_code: int) -> bytes: """ - I've never seen this used. + Used to register felica moble access codes. Will never be used on our network + because we don't implement felica_lookup properly. """ req = ADBFelicaLookupRequest(data) ac = self.data.card.to_access_code(req.idm) @@ -279,8 +292,18 @@ class AimedbServlette(): async def handle_felica_lookup_ex(self, data: bytes, resp_code: int) -> bytes: req = ADBFelicaLookup2Request(data) - access_code = self.data.card.to_access_code(req.idm) - user_id = await self.data.card.get_user_id_from_card(access_code=access_code) + user_id = None + card = await self.data.card.get_card_by_idm(req.idm) + if not card: + access_code = self.data.card.to_access_code(req.idm) + card = await self.data.card.get_card_by_access_code(access_code) + if card: + user_id = card['user'] + await self.data.card.set_idm_by_access_code(access_code, req.idm) + + else: + user_id = card['user'] + access_code = card['access_code'] if user_id is None: user_id = -1 @@ -290,6 +313,14 @@ class AimedbServlette(): ) resp = ADBFelicaLookup2Response.from_req(req.head, user_id, access_code) + + if user_id > 0: + if card['is_banned'] and card['is_locked']: + resp.head.status = ADBStatus.BAN_SYS_USER + elif card['is_banned']: + resp.head.status = ADBStatus.BAN_SYS + elif card['is_locked']: + resp.head.status = ADBStatus.LOCK_USER if user_id and user_id > 0 and self.config.aimedb.id_secret: auth_key = create_sega_auth_key(user_id, req.head.game_id, req.head.store_id, req.head.keychip_id, self.config.aimedb.id_secret, self.config.aimedb.id_lifetime_seconds) @@ -337,6 +368,16 @@ class AimedbServlette(): self.logger.info( f"Registration blocked!: access code {req.access_code}" ) + + if user_id > 0: + if req.access_code.startswith("010") or req.access_code.startswith("3"): + await self.data.card.set_chip_id_by_access_code(req.access_code, req.serial_number) + self.logger.info(f"Attempt to set chip id to {req.serial_number} for access code {req.access_code}") + + elif req.access_code.startswith("0008"): + idm = self.data.card.to_idm(req.access_code) + await self.data.card.set_idm_by_access_code(req.access_code, idm) + self.logger.info(f"Attempt to set IDm to {idm} for access code {req.access_code}") resp = ADBLookupResponse.from_req(req.head, user_id) if resp.user_id <= 0: diff --git a/core/data/alembic/versions/48f4acc43a7e_card_add_idm_chip_id.py b/core/data/alembic/versions/48f4acc43a7e_card_add_idm_chip_id.py new file mode 100644 index 0000000..12fca3a --- /dev/null +++ b/core/data/alembic/versions/48f4acc43a7e_card_add_idm_chip_id.py @@ -0,0 +1,50 @@ +"""card_add_idm_chip_id + +Revision ID: 48f4acc43a7e +Revises: 1e150d16ab6b +Create Date: 2024-06-21 23:53:34.369134 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = '48f4acc43a7e' +down_revision = '1e150d16ab6b' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('aime_card', sa.Column('idm', sa.String(length=16), nullable=True)) + op.add_column('aime_card', sa.Column('chip_id', sa.BIGINT(), nullable=True)) + op.alter_column('aime_card', 'access_code', + existing_type=mysql.VARCHAR(length=20), + nullable=False) + op.alter_column('aime_card', 'created_date', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('now()'), + existing_nullable=True) + op.create_unique_constraint(None, 'aime_card', ['chip_id']) + op.create_unique_constraint(None, 'aime_card', ['idm']) + op.create_unique_constraint(None, 'aime_card', ['access_code']) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'aime_card', type_='unique') + op.drop_constraint(None, 'aime_card', type_='unique') + op.drop_constraint(None, 'aime_card', type_='unique') + op.alter_column('aime_card', 'created_date', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('CURRENT_TIMESTAMP'), + existing_nullable=True) + op.alter_column('aime_card', 'access_code', + existing_type=mysql.VARCHAR(length=20), + nullable=True) + op.drop_column('aime_card', 'chip_id') + op.drop_column('aime_card', 'idm') + # ### end Alembic commands ### diff --git a/core/data/schema/card.py b/core/data/schema/card.py index cd5c647..798dd03 100644 --- a/core/data/schema/card.py +++ b/core/data/schema/card.py @@ -1,6 +1,6 @@ from typing import Dict, List, Optional from sqlalchemy import Table, Column, UniqueConstraint -from sqlalchemy.types import Integer, String, Boolean, TIMESTAMP +from sqlalchemy.types import Integer, String, Boolean, TIMESTAMP, BIGINT from sqlalchemy.sql.schema import ForeignKey from sqlalchemy.sql import func from sqlalchemy.engine import Row @@ -11,12 +11,10 @@ aime_card = Table( "aime_card", metadata, Column("id", Integer, primary_key=True, nullable=False), - Column( - "user", - ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), - nullable=False, - ), - Column("access_code", String(20)), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("access_code", String(20), nullable=False, unique=True), + Column("idm", String(16), unique=True), + Column("chip_id", BIGINT, unique=True), Column("created_date", TIMESTAMP, server_default=func.now()), Column("last_login_date", TIMESTAMP, onupdate=func.now()), Column("is_locked", Boolean, server_default="0"), @@ -122,6 +120,26 @@ class CardData(BaseData): if result is None: self.logger.warn(f"Failed to update last login time for {access_code}") + async def get_card_by_idm(self, idm: str) -> Optional[Row]: + result = await self.execute(aime_card.select(aime_card.c.idm == idm)) + if result: + return result.fetchone() + + async def get_card_by_chip_id(self, chip_id: int) -> Optional[Row]: + result = await self.execute(aime_card.select(aime_card.c.chip_id == chip_id)) + if result: + return result.fetchone() + + async def set_chip_id_by_access_code(self, access_code: str, chip_id: int) -> Optional[Row]: + result = await self.execute(aime_card.update(aime_card.c.access_code == access_code).values(chip_id=chip_id)) + if not result: + self.logger.error(f"Failed to update chip ID to {chip_id} for {access_code}") + + async def set_idm_by_access_code(self, access_code: str, idm: str) -> Optional[Row]: + result = await self.execute(aime_card.update(aime_card.c.access_code == access_code).values(idm=idm)) + if not result: + self.logger.error(f"Failed to update IDm to {idm} for {access_code}") + def to_access_code(self, luid: str) -> str: """ Given a felica cards internal 16 hex character luid, convert it to a 0-padded 20 digit access code as a string @@ -132,4 +150,4 @@ class CardData(BaseData): """ Given a 20 digit access code as a string, return the 16 hex character luid """ - return f"{int(access_code):0{16}x}" + return f"{int(access_code):0{16}X}" diff --git a/titles/pokken/base.py b/titles/pokken/base.py index f864824..ee0d134 100644 --- a/titles/pokken/base.py +++ b/titles/pokken/base.py @@ -165,6 +165,8 @@ class PokkenBase: f"Register new card {access_code} (UserId {user_id}, CardId {card_id})" ) + await self.data.card.set_chip_id_by_access_code(access_code, int(request.load_user.chip_id[:8], 16)) + elif card is None: self.logger.info(f"Registration of card {access_code} blocked!") res.load_user.CopyFrom(load_usr) @@ -173,6 +175,8 @@ class PokkenBase: else: user_id = card['user'] card_id = card['id'] + if not card['chip_id']: + await self.data.card.set_chip_id_by_access_code(access_code, int(request.load_user.chip_id[:8], 16)) """ TODO: Unlock all supports? Probably diff --git a/titles/sao/base.py b/titles/sao/base.py index 80f91a2..76a1def 100644 --- a/titles/sao/base.py +++ b/titles/sao/base.py @@ -83,6 +83,10 @@ class SaoBase: if not user_id: user_id = await self.data.user.create_user() #works card_id = await self.data.card.create_card(user_id, req.access_code) + if req.access_code.startswith("5"): + await self.data.card.set_idm_by_access_code(card_id, req.chip_id[:16]) + elif req.access_code.startswith("010") or req.access_code.startswith("3"): + await self.data.card.set_chip_id_by_access_code(card_id, int(req.chip_id[:8], 16)) if card_id is None: user_id = -1 From ce124ffe13b325675e88cf6f7a642feb0db8e6f1 Mon Sep 17 00:00:00 2001 From: beerpsi Date: Sun, 23 Jun 2024 00:08:53 +0700 Subject: [PATCH 49/76] [chuni] Improve GetGameMapAreaConditionApi --- titles/chuni/const.py | 11 +- titles/chuni/luminous.py | 250 ++++++++++++++++++++++++--------------- 2 files changed, 159 insertions(+), 102 deletions(-) diff --git a/titles/chuni/const.py b/titles/chuni/const.py index 2b79582..d037842 100644 --- a/titles/chuni/const.py +++ b/titles/chuni/const.py @@ -49,11 +49,12 @@ class ChuniConstants: class MapAreaConditionType(Enum): - UNLOCKED = "0" - MAP_AREA_CLEARED = "2" - TROPHY_OBTAINED = "3" + UNLOCKED = 0 + MAP_CLEARED = 1 + MAP_AREA_CLEARED = 2 + TROPHY_OBTAINED = 3 class MapAreaConditionLogicalOperator(Enum): - OR = "0" - AND = "1" + AND = 1 + OR = 2 diff --git a/titles/chuni/luminous.py b/titles/chuni/luminous.py index 2ced55b..2022b34 100644 --- a/titles/chuni/luminous.py +++ b/titles/chuni/luminous.py @@ -1,8 +1,11 @@ +from datetime import timedelta from typing import Dict +from sqlalchemy.dialects.mysql.base import MySQLExecutionContext + from core.config import CoreConfig from titles.chuni.sunplus import ChuniSunPlus -from titles.chuni.const import ChuniConstants, MapAreaConditionLogicalOperator, MapAreaConditionType +from titles.chuni.const import ChuniConstants, MapAreaConditionLogicalOperator, MapAreaConditionType from titles.chuni.config import ChuniConfig @@ -69,176 +72,229 @@ class ChuniLuminous(ChuniSunPlus): async def handle_get_game_map_area_condition_api_request(self, data: Dict) -> Dict: # There is no game data for this, everything is server side. - # TODO: Figure out conditions for 1UM1N0US ep.111 - return { - "length": "7", - "gameMapAreaConditionList": [ - # Secret AREA: MUSIC GAME + # However, we can selectively show/hide events as data is imported into the server. + events = await self.data.static.get_enabled_events(self.version) + event_by_id = {evt["eventId"]: evt for evt in events} + conditions = [] + + # The Mystic Rainbow of LUMINOUS map unlocks when any mainline LUMINOUS area + # (ep. I, ep. II, ep. III) are completed. + mystic_area_1_conditions = { + "mapAreaId": 3229301, # Mystic Rainbow of LUMINOUS Area 1 + "length": 0, + "mapAreaConditionList": [], + } + mystic_area_1_added = False + + # Secret AREA: MUSIC GAME + if 14029 in event_by_id: + start_date = event_by_id[14029]["startDate"].strftime(self.date_time_format) + mission_in_progress_end_date = "2099-12-31 00:00:00.0" + + # The "MISSION in progress" trophy required to trigger the secret area + # is only available in the first CHUNITHM mission. If the second mission + # (event ID 14214) was imported into ARTEMiS, we disable the requirement + # for this trophy. + if 14214 in event_by_id: + mission_in_progress_end_date = (event_by_id[14214]["startDate"] - timedelta(hours=2)).strftime(self.date_time_format) + + conditions.extend([ { - "mapAreaId": "2206201", # BlythE ULTIMA - "length": "1", - # Obtain the trophy "MISSION in progress", which is only available - # when running the first CHUNITHM mission + "mapAreaId": 2206201, # BlythE ULTIMA + "length": 1, + # Obtain the trophy "MISSION in progress". "mapAreaConditionList": [ { "type": MapAreaConditionType.TROPHY_OBTAINED.value, - "conditionId": "6832", + "conditionId": 6832, "logicalOpe": MapAreaConditionLogicalOperator.AND.value, - "startDate": "2023-12-14 07:00:00.0", - "endDate": "2024-01-25 02:00:00.0", + "startDate": start_date, + "endDate": mission_in_progress_end_date, } ], }, { - "mapAreaId": "2206202", # PRIVATE SERVICE ULTIMA - "length": "1", - # Obtain the trophy "MISSION in progress", which is only available - # when running the first CHUNITHM mission + "mapAreaId": 2206202, # PRIVATE SERVICE ULTIMA + "length": 1, + # Obtain the trophy "MISSION in progress". "mapAreaConditionList": [ { "type": MapAreaConditionType.TROPHY_OBTAINED.value, - "conditionId": "6832", + "conditionId": 6832, "logicalOpe": MapAreaConditionLogicalOperator.AND.value, - "startDate": "2023-12-14 07:00:00.0", - "endDate": "2024-01-25 02:00:00.0", + "startDate": start_date, + "endDate": mission_in_progress_end_date, } ], }, { - "mapAreaId": "2206203", # New York Back Raise - "length": "1", + "mapAreaId": 2206203, # New York Back Raise + "length": 1, # SS NightTheater's EXPERT chart and get the title # "今宵、劇場に映し出される景色とは――――。" "mapAreaConditionList": [ { "type": MapAreaConditionType.TROPHY_OBTAINED.value, - "conditionId": "6833", + "conditionId": 6833, "logicalOpe": MapAreaConditionLogicalOperator.AND.value, - "startDate": "2023-12-14 07:00:00.0", + "startDate": start_date, "endDate": "2099-12-31 00:00:00.0", }, ], }, { - "mapAreaId": "2206204", # Spasmodic - "length": "2", + "mapAreaId": 2206204, # Spasmodic + "length": 2, # - Get 1 miss on Random (any difficulty) and get the title "当たり待ち" # - Get 1 miss on 花たちに希望を (any difficulty) and get the title "花たちに希望を" "mapAreaConditionList": [ { "type": MapAreaConditionType.TROPHY_OBTAINED.value, - "conditionId": "6834", + "conditionId": 6834, "logicalOpe": MapAreaConditionLogicalOperator.AND.value, - "startDate": "2023-12-14 07:00:00.0", + "startDate": start_date, "endDate": "2099-12-31 00:00:00.0", }, { "type": MapAreaConditionType.TROPHY_OBTAINED.value, - "conditionId": "6835", + "conditionId": 6835, "logicalOpe": MapAreaConditionLogicalOperator.AND.value, - "startDate": "2023-12-14 07:00:00.0", + "startDate": start_date, "endDate": "2099-12-31 00:00:00.0", }, ], }, { - "mapAreaId": "2206205", # ΩΩPARTS - "length": "2", + "mapAreaId": 2206205, # ΩΩPARTS + "length": 2, # - S Sage EXPERT to get the title "マターリ進行キボンヌ" # - Equip this title and play cab-to-cab with another person with this title - # to get "マターリしようよ" + # to get "マターリしようよ". Disabled because it is difficult to play cab2cab + # on data setups. A network operator may consider re-enabling it by uncommenting + # the second condition. "mapAreaConditionList": [ { "type": MapAreaConditionType.TROPHY_OBTAINED.value, - "conditionId": "6836", + "conditionId": 6836, "logicalOpe": MapAreaConditionLogicalOperator.AND.value, - "startDate": "2023-12-14 07:00:00.0", + "startDate": start_date, "endDate": "2099-12-31 00:00:00.0", }, - { - "type": MapAreaConditionType.TROPHY_OBTAINED.value, - "conditionId": "6837", - "logicalOpe": MapAreaConditionLogicalOperator.AND.value, - "startDate": "2023-12-14 07:00:00.0", - "endDate": "2024-01-25 02:00:00.0", - }, + # { + # "type": MapAreaConditionType.TROPHY_OBTAINED.value, + # "conditionId": 6837, + # "logicalOpe": MapAreaConditionLogicalOperator.AND.value, + # "startDate": start_date, + # "endDate": "2099-12-31 00:00:00.0", + # }, ], }, { - "mapAreaId": "2206206", # Blow My Mind - "length": "1", + "mapAreaId": 2206206, # Blow My Mind + "length": 1, # SS on CHAOS EXPERT, Hydra EXPERT, Surive EXPERT and Jakarta PROGRESSION EXPERT # to get the title "Can you hear me?" "mapAreaConditionList": [ { "type": MapAreaConditionType.TROPHY_OBTAINED.value, - "conditionId": "6838", + "conditionId": 6838, "logicalOpe": MapAreaConditionLogicalOperator.AND.value, - "startDate": "2023-12-14 07:00:00.0", + "startDate": start_date, "endDate": "2099-12-31 00:00:00.0", }, ], }, { - "mapAreaId": "2206207", # VALLIS-NERIA - "length": "6", + "mapAreaId": 2206207, # VALLIS-NERIA + "length": 6, # Finish the 6 other areas "mapAreaConditionList": [ { "type": MapAreaConditionType.MAP_AREA_CLEARED.value, - "conditionId": str(x), + "conditionId": x, "logicalOpe": MapAreaConditionLogicalOperator.AND.value, - "startDate": "2023-12-14 07:00:00.0", + "startDate": start_date, "endDate": "2099-12-31 00:00:00.0", } for x in range(2206201, 2206207) ], }, - # { - # "mapAreaId": "3229301", # Mystic Rainbow of LUMINOUS Area 1 - # "length": "1", - # # Unlocks when any of the mainline LUMINOUS maps are completed? - # "mapAreaConditionList": [ - # # TODO - # ] - # }, - # { - # "mapAreaId": "3229302", # Mystic Rainbow of LUMINOUS Area 2 - # "length": "5", - # # Unlocks when LUMINOUS ep. I is completed - # "mapAreaConditionList": [ - # { - # "type": MapAreaConditionType.MAP_AREA_CLEARED.value, - # "conditionId": str(x), - # "logicalOpe": MapAreaConditionLogicalOperator.AND.value, - # "startDate": "2023-12-14 07:00:00.0", - # "endDate": "2099-12-31 00:00:00.0", - # } - # for x in range(3220101, 3220106) - # ] - # }, - # { - # "mapAreaId": "3229303", # Mystic Rainbow of LUMINOUS Area 3 - # "length": "5", - # # Unlocks when LUMINOUS ep. II is completed - # "mapAreaConditionList": [ - # { - # "type": MapAreaConditionType.MAP_AREA_CLEARED.value, - # "conditionId": str(x), - # "logicalOpe": MapAreaConditionLogicalOperator.AND.value, - # "startDate": "2023-12-14 07:00:00.0", - # "endDate": "2099-12-31 00:00:00.0", - # } - # for x in range(3220201, 3220206) - # ] - # }, - # { - # "mapAreaId": "3229304", # Mystic Rainbow of LUMINOUS Area 4 - # "length": "5", - # # Unlocks when LUMINOUS ep. III is completed - # "mapAreaConditionList": [ - - # ] - # } - ], + ]) + + # LUMINOUS ep. I + if 14005 in event_by_id: + start_date = event_by_id[14005]["startDate"].strftime(self.date_time_format) + + if not mystic_area_1_added: + conditions.append(mystic_area_1_conditions) + mystic_area_1_added = True + + mystic_area_1_conditions["length"] += 1 + mystic_area_1_conditions["mapAreaConditionList"].append( + { + "type": MapAreaConditionType.MAP_CLEARED.value, + "conditionId": 3020701, + "logicalOpe": MapAreaConditionLogicalOperator.OR.value, + "startDate": start_date, + "endDate": "2099-12-31 00:00:00.0", + } + ) + + conditions.append( + { + "mapAreaId": 3229302, # Mystic Rainbow of LUMINOUS Area 2, + "length": 1, + # Unlocks when LUMINOUS ep. I is completed. + "mapAreaConditionList": [ + { + "type": MapAreaConditionType.MAP_CLEARED.value, + "conditionId": 3020701, + "logicalOpe": MapAreaConditionLogicalOperator.AND.value, + "startDate": start_date, + "endDate": "2099-12-31 00:00:00.0", + }, + ], + } + ) + + # LUMINOUS ep. II + if 14250 in event_by_id: + start_date = event_by_id[14250]["startDate"].strftime(self.date_time_format) + + if not mystic_area_1_added: + conditions.append(mystic_area_1_conditions) + mystic_area_1_added = True + + mystic_area_1_conditions["length"] += 1 + mystic_area_1_conditions["mapAreaConditionList"].append( + { + "type": MapAreaConditionType.MAP_CLEARED.value, + "conditionId": 3020702, + "logicalOpe": MapAreaConditionLogicalOperator.OR.value, + "startDate": start_date, + "endDate": "2099-12-31 00:00:00.0", + } + ) + + conditions.append( + { + "mapAreaId": 3229303, # Mystic Rainbow of LUMINOUS Area 3, + "length": 1, + # Unlocks when LUMINOUS ep. II is completed. + "mapAreaConditionList": [ + { + "type": MapAreaConditionType.MAP_CLEARED.value, + "conditionId": 3020702, + "logicalOpe": MapAreaConditionLogicalOperator.AND.value, + "startDate": start_date, + "endDate": "2099-12-31 00:00:00.0", + }, + ], + } + ) + + + return { + "length": len(conditions), + "gameMapAreaConditionList": conditions, } From 203aa43fe15b6ed5abd005fc2565ab4a086105f1 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sat, 22 Jun 2024 13:31:52 -0400 Subject: [PATCH 50/76] chuni: add sun plus and lumi to get_rankings --- titles/chuni/schema/score.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/titles/chuni/schema/score.py b/titles/chuni/schema/score.py index fa95374..766b4b9 100644 --- a/titles/chuni/schema/score.py +++ b/titles/chuni/schema/score.py @@ -242,6 +242,8 @@ class ChuniScoreData(BaseData): # Calculates the ROM version that should be fetched for rankings, based on the game version being retrieved # This prevents tracks that are not accessible in your version from counting towards the 10 results romVer = { + 15: "2.20%", + 14: "2.15%", 13: "2.10%", 12: "2.05%", 11: "2.00%", From f747a731bf89a3081b35b07cc821025330ebe87b Mon Sep 17 00:00:00 2001 From: beerpsi Date: Sun, 23 Jun 2024 01:36:48 +0700 Subject: [PATCH 51/76] [chuni] Use the map flag event ID when checking in GetGameMapAreaConditionApi --- titles/chuni/luminous.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/titles/chuni/luminous.py b/titles/chuni/luminous.py index 2022b34..8f02820 100644 --- a/titles/chuni/luminous.py +++ b/titles/chuni/luminous.py @@ -1,8 +1,6 @@ from datetime import timedelta from typing import Dict -from sqlalchemy.dialects.mysql.base import MySQLExecutionContext - from core.config import CoreConfig from titles.chuni.sunplus import ChuniSunPlus from titles.chuni.const import ChuniConstants, MapAreaConditionLogicalOperator, MapAreaConditionType @@ -258,8 +256,8 @@ class ChuniLuminous(ChuniSunPlus): ) # LUMINOUS ep. II - if 14250 in event_by_id: - start_date = event_by_id[14250]["startDate"].strftime(self.date_time_format) + if 14251 in event_by_id: + start_date = event_by_id[14251]["startDate"].strftime(self.date_time_format) if not mystic_area_1_added: conditions.append(mystic_area_1_conditions) From 699337c41956cc7794cad4d778bfea7148806fb1 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sun, 23 Jun 2024 02:57:37 -0400 Subject: [PATCH 52/76] chuni: fix get_net_battle --- titles/chuni/schema/profile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/titles/chuni/schema/profile.py b/titles/chuni/schema/profile.py index 7ee0164..f47b780 100644 --- a/titles/chuni/schema/profile.py +++ b/titles/chuni/schema/profile.py @@ -816,7 +816,7 @@ class ChuniProfileData(BaseData): return result.inserted_primary_key['id'] self.logger.error(f"Failed to put net battle data for user {user_id}") - async def get_net_battle(self, user_id: int, net_battle_data: Dict) -> Optional[Row]: + async def get_net_battle(self, user_id: int) -> Optional[Row]: result = await self.execute(net_battle.select(net_battle.c.user == user_id)) if result: return result.fetchone() From 714713384d0b3ce4b273bd24d70b4f434a26f7d3 Mon Sep 17 00:00:00 2001 From: zaphkito Date: Sun, 23 Jun 2024 15:26:22 +0800 Subject: [PATCH 53/76] correctly reboot end time in example_config && update docs --- docs/config.md | 4 ++-- example_config/core.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/config.md b/docs/config.md index 39855dc..8a482e3 100644 --- a/docs/config.md +++ b/docs/config.md @@ -17,8 +17,8 @@ - `strict_ip_checking`: Rejects clients if there is no IP in the `arcade` table for the respective arcade. Default `False` ## Title - `loglevel`: Logging level for the title server. Default `info` -- `reboot_start_time`: 24 hour JST time that clients will see as the start of maintenance period, ex `04:00`. Leave blank for no maintenance time. Default: `""` -- `reboot_end_time`: 24 hour JST time that clients will see as the end of maintenance period, ex `05:00`. Leave blank for no maintenance time. Default: `""` +- `reboot_start_time`: 24 hour JST time that clients will see as the start of maintenance period, ex `04:00`. A few games or early version will report errors if it is empty, ex maimai DX 1.00 +- `reboot_end_time`: 24 hour JST time that clients will see as the end of maintenance period, ex `07:00`. this must be set to 7:00 am for some game, please do not change it. ## Database - `host`: Host of the database. Default `localhost` - `username`: Username of the account the server should connect to the database with. Default `aime` diff --git a/example_config/core.yaml b/example_config/core.yaml index 758a089..daf18fc 100644 --- a/example_config/core.yaml +++ b/example_config/core.yaml @@ -18,7 +18,7 @@ server: title: loglevel: "info" reboot_start_time: "04:00" - reboot_end_time: "05:00" + reboot_end_time: "07:00" # this must be set to 7:00 am for some game, please do not change it database: host: "localhost" From acac12c2115a0947398b7b919ed4e011673d0e21 Mon Sep 17 00:00:00 2001 From: zaphkito Date: Sun, 23 Jun 2024 16:17:46 +0800 Subject: [PATCH 54/76] chuni: fix GetUserNetBattleRankingInfoApi --- titles/chuni/schema/profile.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/titles/chuni/schema/profile.py b/titles/chuni/schema/profile.py index f47b780..a8bbb1a 100644 --- a/titles/chuni/schema/profile.py +++ b/titles/chuni/schema/profile.py @@ -793,9 +793,9 @@ class ChuniProfileData(BaseData): versions = [row[0] for row in versions_raw] return sorted(versions, reverse=True) - async def put_net_battle(self, user_id: int, net_battle_data: Dict) -> Optional[int]: + async def put_net_battle(self, aime_id: int, net_battle_data: Dict) -> Optional[int]: sql = insert(net_battle).values( - user=user_id, + user=aime_id, isRankUpChallengeFailed=net_battle_data['isRankUpChallengeFailed'], highestBattleRankId=net_battle_data['highestBattleRankId'], battleIconId=net_battle_data['battleIconId'], @@ -816,7 +816,7 @@ class ChuniProfileData(BaseData): return result.inserted_primary_key['id'] self.logger.error(f"Failed to put net battle data for user {user_id}") - async def get_net_battle(self, user_id: int) -> Optional[Row]: - result = await self.execute(net_battle.select(net_battle.c.user == user_id)) + async def get_net_battle(self, aime_id: int) -> Optional[Row]: + result = await self.execute(net_battle.select(net_battle.c.user == aime_id)) if result: return result.fetchone() From f252b8e3229d3d277bf49bc60b2cde029ae45f61 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sun, 23 Jun 2024 17:41:22 -0400 Subject: [PATCH 55/76] chuni: fix misnamed var in put_net_battle --- titles/chuni/schema/profile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/titles/chuni/schema/profile.py b/titles/chuni/schema/profile.py index a8bbb1a..7587197 100644 --- a/titles/chuni/schema/profile.py +++ b/titles/chuni/schema/profile.py @@ -814,7 +814,7 @@ class ChuniProfileData(BaseData): result = await self.execute(conflict) if result: return result.inserted_primary_key['id'] - self.logger.error(f"Failed to put net battle data for user {user_id}") + self.logger.error(f"Failed to put net battle data for user {aime_id}") async def get_net_battle(self, aime_id: int) -> Optional[Row]: result = await self.execute(net_battle.select(net_battle.c.user == aime_id)) From 068aa5eaa3f11372864d415a95a228c43d4519d8 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sun, 23 Jun 2024 22:09:56 -0400 Subject: [PATCH 56/76] prevent setting chipid if it's 0x04030201 --- core/aimedb.py | 6 +++--- titles/pokken/base.py | 5 +++-- titles/sao/base.py | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/core/aimedb.py b/core/aimedb.py index c6887d3..ab400c2 100644 --- a/core/aimedb.py +++ b/core/aimedb.py @@ -194,9 +194,9 @@ class AimedbServlette(): if user_id and user_id > 0: await self.data.card.update_card_last_login(req.access_code) - if req.access_code.startswith("010") or req.access_code.startswith("3"): + if req.access_code.startswith("010") or req.access_code.startswith("3") and req.serial_number != 0x04030201: # Default segatools sn await self.data.card.set_chip_id_by_access_code(req.access_code, req.serial_number) - self.logger.info(f"Attempt to set chip id to {req.serial_number} for access code {req.access_code}") + self.logger.info(f"Attempt to set chip id to {req.serial_number:08X} for access code {req.access_code}") return ret async def handle_lookup_ex(self, data: bytes, resp_code: int) -> ADBBaseResponse: @@ -370,7 +370,7 @@ class AimedbServlette(): ) if user_id > 0: - if req.access_code.startswith("010") or req.access_code.startswith("3"): + if req.access_code.startswith("010") or req.access_code.startswith("3") and req.serial_number != 0x04030201: # Default segatools sn: await self.data.card.set_chip_id_by_access_code(req.access_code, req.serial_number) self.logger.info(f"Attempt to set chip id to {req.serial_number} for access code {req.access_code}") diff --git a/titles/pokken/base.py b/titles/pokken/base.py index ee0d134..78ca863 100644 --- a/titles/pokken/base.py +++ b/titles/pokken/base.py @@ -165,7 +165,8 @@ class PokkenBase: f"Register new card {access_code} (UserId {user_id}, CardId {card_id})" ) - await self.data.card.set_chip_id_by_access_code(access_code, int(request.load_user.chip_id[:8], 16)) + if int(request.load_user.chip_id[:8], 16) != 0x04030201: + await self.data.card.set_chip_id_by_access_code(access_code, int(request.load_user.chip_id[:8], 16)) elif card is None: self.logger.info(f"Registration of card {access_code} blocked!") @@ -175,7 +176,7 @@ class PokkenBase: else: user_id = card['user'] card_id = card['id'] - if not card['chip_id']: + if not card['chip_id'] and int(request.load_user.chip_id[:8], 16) != 0x04030201: # Default segatools sn: await self.data.card.set_chip_id_by_access_code(access_code, int(request.load_user.chip_id[:8], 16)) """ diff --git a/titles/sao/base.py b/titles/sao/base.py index 76a1def..da982d8 100644 --- a/titles/sao/base.py +++ b/titles/sao/base.py @@ -85,7 +85,7 @@ class SaoBase: card_id = await self.data.card.create_card(user_id, req.access_code) if req.access_code.startswith("5"): await self.data.card.set_idm_by_access_code(card_id, req.chip_id[:16]) - elif req.access_code.startswith("010") or req.access_code.startswith("3"): + elif req.access_code.startswith("010") or req.access_code.startswith("3") and int(req.chip_id[:8], 16) != 0x04030201: await self.data.card.set_chip_id_by_access_code(card_id, int(req.chip_id[:8], 16)) if card_id is None: From bdbb22f164f327fb5625261f3a47d4800c4cfd3d Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sun, 23 Jun 2024 22:19:27 -0400 Subject: [PATCH 57/76] logic fix --- core/aimedb.py | 4 ++-- titles/sao/base.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/aimedb.py b/core/aimedb.py index ab400c2..56490c5 100644 --- a/core/aimedb.py +++ b/core/aimedb.py @@ -194,7 +194,7 @@ class AimedbServlette(): if user_id and user_id > 0: await self.data.card.update_card_last_login(req.access_code) - if req.access_code.startswith("010") or req.access_code.startswith("3") and req.serial_number != 0x04030201: # Default segatools sn + if (req.access_code.startswith("010") or req.access_code.startswith("3")) and req.serial_number != 0x04030201: # Default segatools sn await self.data.card.set_chip_id_by_access_code(req.access_code, req.serial_number) self.logger.info(f"Attempt to set chip id to {req.serial_number:08X} for access code {req.access_code}") return ret @@ -370,7 +370,7 @@ class AimedbServlette(): ) if user_id > 0: - if req.access_code.startswith("010") or req.access_code.startswith("3") and req.serial_number != 0x04030201: # Default segatools sn: + if (req.access_code.startswith("010") or req.access_code.startswith("3")) and req.serial_number != 0x04030201: # Default segatools sn: await self.data.card.set_chip_id_by_access_code(req.access_code, req.serial_number) self.logger.info(f"Attempt to set chip id to {req.serial_number} for access code {req.access_code}") diff --git a/titles/sao/base.py b/titles/sao/base.py index da982d8..8d5650d 100644 --- a/titles/sao/base.py +++ b/titles/sao/base.py @@ -85,7 +85,7 @@ class SaoBase: card_id = await self.data.card.create_card(user_id, req.access_code) if req.access_code.startswith("5"): await self.data.card.set_idm_by_access_code(card_id, req.chip_id[:16]) - elif req.access_code.startswith("010") or req.access_code.startswith("3") and int(req.chip_id[:8], 16) != 0x04030201: + elif (req.access_code.startswith("010") or req.access_code.startswith("3")) and int(req.chip_id[:8], 16) != 0x04030201: await self.data.card.set_chip_id_by_access_code(card_id, int(req.chip_id[:8], 16)) if card_id is None: From 8f9e9d4766dfb03a19eb5f4a1ccb8d0be39bca8f Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Mon, 24 Jun 2024 13:37:10 -0400 Subject: [PATCH 58/76] fix downgrade script issue referenced in #156 --- .../alembic/versions/48f4acc43a7e_card_add_idm_chip_id.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/data/alembic/versions/48f4acc43a7e_card_add_idm_chip_id.py b/core/data/alembic/versions/48f4acc43a7e_card_add_idm_chip_id.py index 12fca3a..98872e8 100644 --- a/core/data/alembic/versions/48f4acc43a7e_card_add_idm_chip_id.py +++ b/core/data/alembic/versions/48f4acc43a7e_card_add_idm_chip_id.py @@ -35,9 +35,9 @@ def upgrade(): def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint(None, 'aime_card', type_='unique') - op.drop_constraint(None, 'aime_card', type_='unique') - op.drop_constraint(None, 'aime_card', type_='unique') + op.drop_constraint("chip_id", 'aime_card', type_='unique') + op.drop_constraint("idm", 'aime_card', type_='unique') + op.drop_constraint("access_code", 'aime_card', type_='unique') op.alter_column('aime_card', 'created_date', existing_type=mysql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), From 05c4035f5301e260d19758a778bc36dbe2a73e6e Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Mon, 24 Jun 2024 18:11:25 -0400 Subject: [PATCH 59/76] docs: add instructions for migrating from old versions, remove obsolite docs, add openssl example to windows guide --- docs/INSTALL_UBUNTU_old.md | 129 ------------------------------------ docs/INSTALL_WINDOWS.md | 2 +- docs/INSTALL_WINDOWS_old.md | 102 ---------------------------- docs/migrating.md | 34 ++++++++++ 4 files changed, 35 insertions(+), 232 deletions(-) delete mode 100644 docs/INSTALL_UBUNTU_old.md delete mode 100644 docs/INSTALL_WINDOWS_old.md create mode 100644 docs/migrating.md diff --git a/docs/INSTALL_UBUNTU_old.md b/docs/INSTALL_UBUNTU_old.md deleted file mode 100644 index 710c757..0000000 --- a/docs/INSTALL_UBUNTU_old.md +++ /dev/null @@ -1,129 +0,0 @@ -# ARTEMiS - Ubuntu 20.04 LTS Guide -This step-by-step guide assumes that you are using a fresh install of Ubuntu 20.04 LTS, some of the steps can be skipped if you already have an installation with MySQL 5.7 or even some of the modules already present on your environment - -# Setup -## Install memcached module -1. sudo apt-get install memcached -2. Under the file /etc/memcached.conf, please make sure the following parameters are set: - -``` -# Start with a cap of 64 megs of memory. It's reasonable, and the daemon default -# Note that the daemon will grow to this size, but does not start out holding this much -# memory - --I 128m --m 1024 -``` - -** This is mandatory to avoid memcached overload caused by Crossbeats or by massive profiles - -3. Restart memcached using: sudo systemctl restart memcached - -## Install MySQL 5.7 -``` -sudo apt update -sudo apt install wget -y -wget https://dev.mysql.com/get/mysql-apt-config_0.8.12-1_all.deb -sudo dpkg -i mysql-apt-config_0.8.12-1_all.deb -``` - 1. During the first prompt, select Ubuntu Bionic - 2. Select the default option - 3. Select MySQL 5.7 - 4. Select the last option -``` -sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 467B942D3A79BD29 -sudo apt-get update -sudo apt-cache policy mysql-server -sudo apt install -f mysql-client=5.7* mysql-community-server=5.7* mysql-server=5.7* -``` - -## Default Configuration for MySQL Server -1. sudo mysql_secure_installation -> Make sure to follow the steps that will be prompted such as changing the mysql root password and such - -2. Test your MySQL Server login by doing the following command : -> mysql -u root -p - -## Create the default ARTEMiS database and user -1. mysql -u root -p -2. Please change the password indicated in the next line for a custom secure one and continue with the next commands - -``` -CREATE USER 'aime'@'localhost' IDENTIFIED BY 'MyStrongPass.'; -CREATE DATABASE aime; -GRANT Alter,Create,Delete,Drop,Index,Insert,References,Select,Update ON aime.* TO 'aime'@'localhost'; -FLUSH PRIVILEGES; -exit; -``` - -3. sudo systemctl restart mysql - -## Install Python modules -``` -sudo apt-get install python3-dev default-libmysqlclient-dev build-essential mysql-client libmysqlclient-dev libmemcached-dev -sudo apt install libpython3.8-dev -sudo apt-get install python3-software-properties -sudo apt install python3-pip -sudo pip3 install --upgrade pip testresources -sudo pip3 install --upgrade pip setuptools -sudo apt-get install python3-tk -``` -7. Change your work path to the ARTEMiS root folder using 'cd' and install the requirements: -> sudo python3 -m pip install -r requirements.txt - -## Copy/Rename the folder example_config to config - -## Adjust /config/core.yaml -1. Make sure to change the server listen_address to be set to your local machine IP (ex.: 192.168.1.xxx) -2. Adjust the proper MySQL information you created earlier -3. Add the AimeDB key at the bottom of the file - -## Create the database tables for ARTEMiS -1. sudo python3 dbutils.py create - -2. If you get "No module named Crypto", run the following command: -``` -sudo pip uninstall crypto -sudo pip uninstall pycrypto -sudo pip install pycrypto -``` - -## Firewall Adjustements -``` -sudo ufw allow 80 -sudo ufw allow 443 -sudo ufw allow 8443 -sudo ufw allow 22345 -sudo ufw allow 8090 -sudo ufw allow 8444 -sudo ufw allow 8080 -``` - -## Running the ARTEMiS instance -1. sudo python3 index.py - -# Troubleshooting - -## Game does not connect to ARTEMiS Allnet server -1. Double-check your core.yaml, the listen_address is most likely either not binded to the proper IP or the port is not opened - -## Game does not connect to Title Server -1. Verify that your core.yaml is setup properly for both the server listen_address and title hostname -2. Boot your game and verify that an AllNet response does show and if it does, attempt to open the URI that is shown under a browser such as Edge, Chrome & Firefox. -3. If a page is shown, the server is working properly and if it doesn't, double check your port forwarding and also that you have entered the proper local IP under the Title hostname in core.yaml. - -## Unhandled command under AimeDB -1. Double check your AimeDB key under core.yaml, it is incorrect. - -## Memcache failed, error 3 -1. Make sure memcached is properly installed and running. You can check the status of the service using the following command: -> sudo systemctl status memcached -2. If it is failing, double check the /etc/memcached.conf file, it may have duplicated arguments like the -I and -m -3. If it is still not working afterward, you can proceed with a workaround by manually editing the /core/data/cache.py file. -``` -# Make memcache optional -try: - has_mc = False -except ModuleNotFoundError: - has_mc = False -``` diff --git a/docs/INSTALL_WINDOWS.md b/docs/INSTALL_WINDOWS.md index 1cbe140..d17886a 100644 --- a/docs/INSTALL_WINDOWS.md +++ b/docs/INSTALL_WINDOWS.md @@ -59,7 +59,7 @@ GRANT Alter,Create,Delete,Drop,Index,Insert,References,Select,Update ON aime.* T - Put the password you created for the aime user into the `database` section. - Put in the aimedb key (YOU DO NOT GENERATE THIS KEY, FIND IT SOMEWHERE). - Set your hostname to be whatever hostname or IP address games can reach your server at (many games reject localhost and 127.0.0.1). - - Optional: generate base64-encoded secrets for aimedb and frontend. + - Optional: generate base64-encoded secrets for aimedb and frontend using something like `openssl rand -base64 64`. It is advised to make all secrets different. - See [config.md](docs/config.md) for a full list of options. - edit `idz.yaml` - If you don't plan on anyone using your server to play Initial D Zero, it is best to disable it to cut down on console spam on boot. diff --git a/docs/INSTALL_WINDOWS_old.md b/docs/INSTALL_WINDOWS_old.md deleted file mode 100644 index b976b26..0000000 --- a/docs/INSTALL_WINDOWS_old.md +++ /dev/null @@ -1,102 +0,0 @@ -# ARTEMiS - Windows 10/11 Guide -This step-by-step guide assumes that you are using a fresh install of Windows 10/11 without MySQL installed, some of the steps can be skipped if you already have an installation with MySQL 8.0 or even some of the modules already present on your environment - -# Setup -## Install Python Python 3.9 (recommended) or 3.10 -1. Download Python 3.9 : [Link](https://www.python.org/ftp/python/3.9.13/python-3.9.13-amd64.exe) -2. Install python-3.9.13-amd64.exe - 1. Select Customize installation - 2. Make sure that pip, tcl/tk, and the for all users are checked and hit Next - 3. Make sure that you enable "Create shortcuts for installed applications" and "Add Python to environment variables" and hit Install - -## Install MySQL 8.0 -1. Download MySQL 8.0 Server : [Link](https://dev.mysql.com/get/Downloads/MySQLInstaller/mysql-installer-community-8.0.34.0.msi) -2. Install mysql-installer-web-community-8.0.34.0.msi - 1. Click on "Add ..." on the side - 2. Click on the "+" next to MySQL Servers - 3. Make sure MySQL Server 8.0.34 - X64 is under the products to be installed. - 4. Hit Next and Next once installed - 5. Select the configuration type "Development Computer" - 6. Hit Next - 7. Select "Use Legacy Authentication Method (Retain MySQL 5.x compatibility)" and hit Next - 8. Enter a root password and then hit Next > - 9. Leave everything under Windows Service as default and hit Next > - 10. Click on Execute and for it to finish and hit Next> and then Finish -3. Open MySQL 8.0 Command Line Client and login as your root user -4. Change `` to a new password for the user aime, type those commands to create your user and the database - -```sql -CREATE USER 'aime'@'localhost' IDENTIFIED BY ''; -CREATE DATABASE aime; -GRANT Alter,Create,Delete,Drop,Index,Insert,References,Select,Update ON aime.* TO 'aime'@'localhost'; -FLUSH PRIVILEGES; -exit; -``` - -## Install Python modules -1. Change your work path to the artemis-master folder using 'cd' and install the requirements: - -```shell -pip install -r requirements.txt -``` - -## Copy/Rename the folder `example_config` to `config` - -## Adjust `config/core.yaml` - -1. Make sure to change the server `hostname` to be set to your local machine IP (ex.: 192.168.xxx.xxx) - - In case you want to run this only locally, set the following values: - -```yaml -server: - listen_address: 0.0.0.0 -title: - hostname: 192.168.xxx.xxx -``` - -1. Adjust the proper MySQL information you created earlier -```yaml -database: - host: "localhost" - username: "aime" - password: "" - name: "aime" -``` -3. Add the AimeDB key at the bottom of the file -4. If the webui is needed, change the flag from False to True - -## Create the database tables for ARTEMiS - -```shell -python dbutils.py create -``` - -## Firewall Adjustements -Make sure the following ports are open both on your router and local Windows firewall in case you want to use this for public use (NOT recommended): -> Port 80 (TCP), 443 (TCP), 8443 (TCP), 22345 (TCP), 8080 (TCP), 8090 (TCP) **webui, 8444 (TCP) **mucha - -## Running the ARTEMiS instance -```shell -python index.py -``` - -# Troubleshooting - -## Game does not connect to ARTEMiS Allnet server -1. Double-check your core.yaml, the listen_address is most likely either not binded to the proper IP or the port is not opened - -## Game does not connect to Title Server -1. Verify that your core.yaml is setup properly for both the server listen_address and title hostname -2. Boot your game and verify that an AllNet response does show and if it does, attempt to open the URI that is shown under a browser such as Edge, Chrome & Firefox. -3. If a page is shown, the server is working properly and if it doesn't, double check your port forwarding and also that you have entered the proper local IP under the Title hostname in core.yaml. - -## Unhandled command under AimeDB -1. Double check your AimeDB key under core.yaml, it is incorrect. - -## AttributeError: module 'collections' has no attribute 'Hashable' -1. This means the pyYAML module is obsolete, simply rerun pip with the -U (force update) flag, as shown below. - - Change your work path to the artemis-master (or artemis-develop) folder using 'cd' and run the following commands: - -```shell -pip install -r requirements.txt -U -``` diff --git a/docs/migrating.md b/docs/migrating.md new file mode 100644 index 0000000..3e2e056 --- /dev/null +++ b/docs/migrating.md @@ -0,0 +1,34 @@ +# Migrating from an older build of ARTEMiS +If you haven't updated artemis in a while, you may find that configuration options have moved, been renamed, or no longer exist. This document exists to help migrate from legacy versions of artemis to newer builds. + +## Dependancies +Make sure your dependiences are up to date with what's required to run artemis. A simple `pip install -r requirements.txt` will get you up to date. + +## Database +Database migration is required if you are using a version of artemis that still uses the old custom-rolled database versioning system (raw SQL scripts). Artemis now uses alembic to manage database versioning, and you will need to move to this new system. + +**BEFORE DOING ANY DATABASE WORK, ALWAYS MAKE SURE YOU HAVE FUNCTIONAL, UP-TO-DATE BACKUPS!!** + +For almost all situations, simply running `python dbutils.py migrate` will do the job. This will upgrade you to the latest version of the old system, move you over to alembic, then upgrade you to the newest alembic version. If you encounter any errors or data loss, you should report this as a bug to our issue tracker. + +## Configuration +Configuration management is the sewage cleaning of the sysadmin world. It sucks and nobody likes to do it, but it needs to be done or everyone ends up in deep shit. This section will walk through what configuration options have changed, and how to set them properly. + +### core.yaml +`title`->`hostname` is now `server`->`hostname`. This hostname is what gets sent to clients in response to auth requests, so it should be both accessable from whereever the client is, and point properly to the title server. + +With the move to starlette and uvicorn, different services now run as seperate USGI applications. `billing`->`standalone` and `allnet`->`standalone` are flags that determine weather the service runs as a stand-alone service, on it's own seperate port, or as a part of the whole application. For example, setting `billing`->`standalone` to `True` will cause a seperate instance of the billing server to spin up listening on 8443 with SSL using the certs listed in the config file. Setting it to `False` will just allow the main server to also serve `/request/` and assumes that something is standing in front of it proxying 8443 SSL to whatever `server`->`port` is set to. + +Beforehand, if `server`->`is_develop` was `False`, the server assumed that there was a proxy standing in front of it, proxying requests to proper channels. This was, in hindsight, a very dumb assumption. Now, `server`->`is_using_proxy` is what flags the server as having nginx or another proxy in front of it. The effects of setting this to true are somewhat game-dependant, but generally artemis will use the port listed in `server`->`proxy_port` (and `server`->`proxy_port_ssl` for SSL connections, as defined by the games) instead of `server`->`port`. If set to 0, `server`->`proxy_port` will default to what `server`->`port` (and `server`->`proxy_port_ssl` will default to 443) make sure to set them accordingly. Note that some title servers have their own needs and specify their own specific ports. Refer to [game_specific_info.md](docs/game_specific_info.md) for more infomation. (For example, pokken requires SSL using an older, weaker certificate, and always requires the port to be sent even if it's port 443) + +`index.py`'s args have changed. You can now override what port the title server listens on with `-p` and tell the server to use ssl with `-s`. + +Rather then having a `standalone` config variable, the frontend is a seperate wsgi app entirely. Having `enable` be `True` will launch it on the port specified in the config file. Otherwise, the fontend will not run. + +`title`->`reboot_start_time`/`reboot_end_time` allow you to specify when the games should be told network maintanence is happening. It's exact implementation depends on the game. Do note that many games will beave unexpectly if `reboot_end_time` is not `07:00`. + +If you wish to make use of aimedb's SegaAuthId system to better protect the few title servers that actually use it, set `aimedb`->`id_secret` to base64-encoded random bytes (32 is a good length) using something like `openssl rand -base64 64`. If you intend to use the frontend, the same thing must be done for `frontend`->`secret` or you won't be able to log in. + +`mucha`'s only option is now just log level. + +`aimedb` now has it's own `listen_address` field, in case you want to proxy everything but aimedb, so it can still listen on `0.0.0.0` instead of `127.0.0.1`. From 8e39b56bae2339e7a97eef3625f0ef63ec2a5798 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Mon, 24 Jun 2024 23:09:32 +0000 Subject: [PATCH 60/76] CxB: fix default config --- example_config/cxb.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example_config/cxb.yaml b/example_config/cxb.yaml index 5cc4f90..628d49c 100644 --- a/example_config/cxb.yaml +++ b/example_config/cxb.yaml @@ -1,4 +1,4 @@ server: enable: True loglevel: "info" - use:https: True \ No newline at end of file + use_https: True \ No newline at end of file From 6ae11f96a29dbaea1891c7b75f23ab80057deea8 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Mon, 24 Jun 2024 22:28:22 -0400 Subject: [PATCH 61/76] aimedb: fix idm zfill issue --- core/aimedb.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/core/aimedb.py b/core/aimedb.py index 56490c5..88796c7 100644 --- a/core/aimedb.py +++ b/core/aimedb.py @@ -240,18 +240,19 @@ class AimedbServlette(): used on the big boy networks. """ req = ADBFelicaLookupRequest(data) - card = await self.data.card.get_card_by_idm(req.idm) + idm = req.idm.zfill(16) + card = await self.data.card.get_card_by_idm(idm) if not card: - ac = self.data.card.to_access_code(req.idm) + ac = self.data.card.to_access_code(idm) test = await self.data.card.get_card_by_access_code(ac) if test: - await self.data.card.set_idm_by_access_code(ac, req.idm) + await self.data.card.set_idm_by_access_code(ac, idm) else: ac = card['access_code'] self.logger.info( - f"idm {req.idm} ipm {req.pmm} -> access_code {ac}" + f"idm {idm} ipm {req.pmm.zfill(16)} -> access_code {ac}" ) return ADBFelicaLookupResponse.from_req(req.head, ac) @@ -293,13 +294,14 @@ class AimedbServlette(): async def handle_felica_lookup_ex(self, data: bytes, resp_code: int) -> bytes: req = ADBFelicaLookup2Request(data) user_id = None - card = await self.data.card.get_card_by_idm(req.idm) + idm = req.idm.zfill(16) + card = await self.data.card.get_card_by_idm(idm) if not card: - access_code = self.data.card.to_access_code(req.idm) + access_code = self.data.card.to_access_code(idm) card = await self.data.card.get_card_by_access_code(access_code) if card: user_id = card['user'] - await self.data.card.set_idm_by_access_code(access_code, req.idm) + await self.data.card.set_idm_by_access_code(access_code, idm) else: user_id = card['user'] @@ -309,7 +311,7 @@ class AimedbServlette(): user_id = -1 self.logger.info( - f"idm {req.idm} ipm {req.pmm} -> access_code {access_code} user_id {user_id}" + f"idm {idm} ipm {req.pmm} -> access_code {access_code} user_id {user_id}" ) resp = ADBFelicaLookup2Response.from_req(req.head, user_id, access_code) From e91f84fecc7c6136fb05249cca3cf706cc973c62 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Tue, 25 Jun 2024 14:02:53 -0400 Subject: [PATCH 62/76] sao: backport changes from diana --- .../versions/a616fd164e40_sao_backport.py | 723 + core/data/schema/card.py | 8 + example_config/sao.yaml | 11 +- titles/sao/__init__.py | 2 + titles/sao/base.py | 3153 +++- titles/sao/config.py | 44 +- titles/sao/const.py | 657 +- titles/sao/data/1/AcLoginBonuses.csv | Bin 0 -> 283 bytes titles/sao/data/1/AdBanners.csv | Bin 0 -> 59 bytes titles/sao/data/1/AppointLeaderEffect.csv | Bin 0 -> 925 bytes titles/sao/data/1/AppointLeaderEffectType.csv | Bin 0 -> 500 bytes titles/sao/data/1/AppointLeaderParam.csv | Bin 0 -> 166 bytes titles/sao/data/1/Awakening.csv | Bin 0 -> 159 bytes titles/sao/data/1/BattleCamera.csv | Bin 0 -> 162 bytes .../sao/data/1/BeginnerMissionConditions.csv | Bin 0 -> 2079 bytes titles/sao/data/1/BeginnerMissionRewards.csv | Bin 0 -> 812 bytes .../data/1/BeginnerMissionSeatConditions.csv | Bin 0 -> 419 bytes .../sao/data/1/BeginnerMissionSeatRewards.csv | Bin 0 -> 401 bytes titles/sao/data/1/BeginnerMissions.csv | Bin 0 -> 121 bytes titles/sao/data/1/BnidSerialCodeRewards.csv | Bin 0 -> 301 bytes titles/sao/data/1/BnidSerialCodes.csv | Bin 0 -> 92 bytes titles/sao/data/1/BossAttack.csv | Bin 0 -> 404017 bytes titles/sao/data/1/BuffItem.csv | Bin 0 -> 417 bytes titles/sao/data/1/BuyTicket.csv | Bin 0 -> 300 bytes titles/sao/data/1/Cap.csv | Bin 0 -> 27 bytes titles/sao/data/1/Chara.csv | Bin 0 -> 945 bytes titles/sao/data/1/CharaAttack.csv | Bin 0 -> 163449 bytes titles/sao/data/1/CharaComment.csv | Bin 0 -> 35771 bytes titles/sao/data/1/CharaFriendlyRank.csv | Bin 0 -> 913 bytes titles/sao/data/1/ChatEventStory.csv | Bin 0 -> 5154 bytes titles/sao/data/1/ChatMainStory.csv | Bin 0 -> 2392 bytes titles/sao/data/1/ChatSideStory.csv | Bin 0 -> 122156 bytes titles/sao/data/1/ComebackEvent.csv | Bin 0 -> 417 bytes titles/sao/data/1/CompositionEvent.csv | Bin 0 -> 129 bytes titles/sao/data/1/CompositionParam.csv | Bin 0 -> 104 bytes titles/sao/data/1/CostumeType.csv | Bin 0 -> 209 bytes titles/sao/data/1/DebugValidator.csv | Bin 0 -> 356 bytes titles/sao/data/1/DefragMatchAIPattern.csv | Bin 0 -> 274 bytes titles/sao/data/1/DefragMatchBossTable.csv | Bin 0 -> 8889 bytes titles/sao/data/1/DefragMatchChat.csv | Bin 0 -> 760 bytes .../sao/data/1/DefragMatchCpuSupportLogs.csv | Bin 0 -> 726 bytes titles/sao/data/1/DefragMatchCpuUnits.csv | Bin 0 -> 11687 bytes titles/sao/data/1/DefragMatchGrade.csv | Bin 0 -> 22852 bytes .../sao/data/1/DefragMatchPeriodBonuses.csv | Bin 0 -> 134736 bytes titles/sao/data/1/DefragMatchQuest.csv | Bin 0 -> 2205 bytes .../1/DefragMatchRandomBonusConditions.csv | Bin 0 -> 965 bytes .../data/1/DefragMatchRandomBonusTables.csv | Bin 0 -> 10097 bytes titles/sao/data/1/DefragMatchRareDrops.csv | Bin 0 -> 100597 bytes titles/sao/data/1/DefragMatchSeed.csv | Bin 0 -> 1372 bytes .../sao/data/1/DefragMatchSpecialEffects.csv | Bin 0 -> 1086 bytes titles/sao/data/1/DefragMatchs.csv | Bin 0 -> 1235 bytes titles/sao/data/1/Enemy.csv | Bin 0 -> 3606 bytes titles/sao/data/1/EnemyKind.csv | Bin 0 -> 49181 bytes titles/sao/data/1/EnemySet.csv | Bin 0 -> 1580 bytes titles/sao/data/1/Episode.csv | Bin 0 -> 12568 bytes titles/sao/data/1/EpisodeAppends.csv | Bin 0 -> 231 bytes titles/sao/data/1/EpisodeChapter.csv | Bin 0 -> 227 bytes titles/sao/data/1/EpisodePart.csv | Bin 0 -> 50 bytes titles/sao/data/{ => 1}/Equipment.csv | Bin 30612 -> 30498 bytes titles/sao/data/1/EquipmentLevel.csv | Bin 0 -> 2047 bytes titles/sao/data/1/Event.csv | Bin 0 -> 328 bytes titles/sao/data/1/EventItems.csv | Bin 0 -> 1656 bytes titles/sao/data/1/EventMonsters.csv | Bin 0 -> 548 bytes titles/sao/data/1/EventScenes.csv | Bin 0 -> 9002 bytes titles/sao/data/1/ExBonusCondition.csv | Bin 0 -> 740 bytes titles/sao/data/1/ExBonusTable.csv | Bin 0 -> 44094 bytes titles/sao/data/1/ExTowerQuests.csv | Bin 0 -> 4729 bytes titles/sao/data/1/ExTowers.csv | Bin 0 -> 472 bytes .../data/1/FirstTicketPurchaseCampaigns.csv | Bin 0 -> 278 bytes titles/sao/data/1/FixedFormChat.csv | Bin 0 -> 1545 bytes titles/sao/data/1/FixedFormChatText.csv | Bin 0 -> 15572 bytes titles/sao/data/1/Fragment.csv | Bin 0 -> 144 bytes titles/sao/data/1/GamePlayPrice.csv | Bin 0 -> 240 bytes titles/sao/data/1/GashaFreeCampaigns.csv | Bin 0 -> 139 bytes titles/sao/data/1/GashaMedalBonuses.csv | Bin 0 -> 317 bytes titles/sao/data/1/GashaMedalSettings.csv | Bin 0 -> 2700 bytes titles/sao/data/1/GashaMedalShopItems.csv | Bin 0 -> 47802 bytes titles/sao/data/{ => 1}/GashaMedalShops.csv | Bin 84824 -> 85709 bytes titles/sao/data/1/GashaMedalTypes.csv | Bin 0 -> 280 bytes titles/sao/data/1/GashaMedals.csv | Bin 0 -> 3172 bytes titles/sao/data/1/GenericCampaignPeriods.csv | Bin 0 -> 210 bytes titles/sao/data/1/GimmickAttack.csv | Bin 0 -> 18790 bytes titles/sao/data/{ => 1}/HeroLog.csv | Bin 306201 -> 305719 bytes titles/sao/data/1/HeroLogLevel.csv | Bin 0 -> 2141 bytes titles/sao/data/1/HeroLogRole.csv | Bin 0 -> 98 bytes titles/sao/data/1/HeroLogTrustRank.csv | Bin 0 -> 912 bytes titles/sao/data/{ => 1}/Item.csv | Bin 43814 -> 43610 bytes titles/sao/data/1/ItemType.csv | Bin 0 -> 412 bytes .../data/1/LinkedSiteRegCampaignRewards.csv | Bin 0 -> 371 bytes titles/sao/data/1/LinkedSiteRegCampaigns.csv | Bin 0 -> 269 bytes titles/sao/data/1/MenuDisplayEnemy.csv | Bin 0 -> 18768 bytes titles/sao/data/1/Mission.csv | Bin 0 -> 3747 bytes titles/sao/data/1/MissionClearText.csv | Bin 0 -> 1716 bytes titles/sao/data/1/MissionDifficulty.csv | Bin 0 -> 92 bytes titles/sao/data/1/MissionTable.csv | Bin 0 -> 830 bytes titles/sao/data/1/MonsterAction.csv | Bin 0 -> 738612 bytes titles/sao/data/1/MonsterAttack.csv | Bin 0 -> 76672 bytes titles/sao/data/1/NavigatorChara.csv | Bin 0 -> 103 bytes titles/sao/data/1/NavigatorComment.csv | Bin 0 -> 25795 bytes titles/sao/data/1/PayingYuiMedalBonuses.csv | Bin 0 -> 241 bytes titles/sao/data/1/PlayCampaignRewards.csv | Bin 0 -> 451 bytes titles/sao/data/1/PlayCampaigns.csv | Bin 0 -> 243 bytes titles/sao/data/1/PlayerRank.csv | Bin 0 -> 5656 bytes titles/sao/data/1/PlayerTraceTable.csv | Bin 0 -> 554 bytes titles/sao/data/1/Property.csv | Bin 0 -> 19611 bytes titles/sao/data/1/PropertyTable.csv | Bin 0 -> 155348 bytes titles/sao/data/1/PropertyType.csv | Bin 0 -> 5947 bytes titles/sao/data/1/QuestDropBoostCampaigns.csv | Bin 0 -> 204 bytes .../sao/data/1/QuestEpisodeAppendRewards.csv | Bin 0 -> 4996 bytes titles/sao/data/1/QuestExistUnit.csv | Bin 0 -> 173760 bytes titles/sao/data/1/QuestParam.csv | Bin 0 -> 91 bytes titles/sao/data/1/QuestRareDrop.csv | Bin 0 -> 40609 bytes titles/sao/data/{ => 1}/QuestScene.csv | Bin 68974 -> 68734 bytes .../data/1/QuestSpecialRareDropSettings.csv | Bin 0 -> 394 bytes titles/sao/data/1/QuestSpecialRareDrops.csv | Bin 0 -> 249 bytes titles/sao/data/1/QuestTutorial.csv | Bin 0 -> 6441 bytes titles/sao/data/1/Rarity.csv | Bin 0 -> 177 bytes .../sao/data/1/ResEarnCampaignShopItems.csv | Bin 0 -> 640 bytes titles/sao/data/1/ResEarnCampaignShops.csv | Bin 0 -> 826 bytes titles/sao/data/1/RewardSet.csv | Bin 0 -> 1241 bytes titles/sao/data/1/RewardTable.csv | Bin 0 -> 820935 bytes titles/sao/data/1/SideQuest.csv | Bin 0 -> 2554 bytes titles/sao/data/1/Skill.csv | Bin 0 -> 153697 bytes titles/sao/data/1/SkillCutInTextIndex.csv | Bin 0 -> 37061 bytes titles/sao/data/1/SkillCutin.csv | Bin 0 -> 63707 bytes titles/sao/data/1/SkillLevel.csv | Bin 0 -> 96 bytes titles/sao/data/1/SkillTable.csv | Bin 0 -> 226670 bytes titles/sao/data/1/SoundBgm.csv | Bin 0 -> 2890 bytes titles/sao/data/1/SoundSe.csv | Bin 0 -> 146198 bytes titles/sao/data/1/SoundUi.csv | Bin 0 -> 14151 bytes titles/sao/data/1/SoundVoice.csv | Bin 0 -> 364166 bytes titles/sao/data/1/Sound_SkillCutinVoice.csv | Bin 0 -> 12145 bytes titles/sao/data/1/SubStateMonster.csv | Bin 0 -> 17087 bytes titles/sao/data/{ => 1}/SupportLog.csv | Bin 379372 -> 378891 bytes titles/sao/data/1/SupportLogType.csv | Bin 0 -> 62 bytes titles/sao/data/1/SynchroSkill.csv | Bin 0 -> 5631 bytes titles/sao/data/1/Tips.csv | Bin 0 -> 30535 bytes titles/sao/data/1/Title.csv | Bin 0 -> 15959 bytes titles/sao/data/1/TreasureHuntTaskText.csv | Bin 0 -> 1014 bytes titles/sao/data/1/TrialTower.csv | Bin 0 -> 2116 bytes titles/sao/data/1/UnanalyzedLogGrade.csv | Bin 0 -> 170 bytes titles/sao/data/1/Unit.csv | Bin 0 -> 561120 bytes titles/sao/data/1/UnitCollision.csv | Bin 0 -> 365911 bytes titles/sao/data/1/UnitGimmick.csv | Bin 0 -> 15582 bytes titles/sao/data/1/UnitPower.csv | Bin 0 -> 104626 bytes titles/sao/data/1/WeaponType.csv | Bin 0 -> 843 bytes titles/sao/data/1/YuiMedalBonus.csv | Bin 0 -> 172 bytes titles/sao/data/1/YuiMedalBonusCondition.csv | Bin 0 -> 174 bytes titles/sao/data/1/YuiMedalGetCondition.csv | Bin 0 -> 864 bytes titles/sao/data/1/YuiMedalShopItems.csv | Bin 0 -> 41960 bytes titles/sao/data/{ => 1}/YuiMedalShops.csv | Bin 94750 -> 95542 bytes titles/sao/data/EquipmentLevel.csv | Bin 1211 -> 0 bytes titles/sao/data/GashaMedalShopItems.csv | Bin 46917 -> 0 bytes titles/sao/data/HeroLogLevel.csv | Bin 1249 -> 0 bytes titles/sao/data/PlayerRank.csv | Bin 3548 -> 0 bytes titles/sao/data/RareDropTable.csv | Bin 11849 -> 0 bytes titles/sao/data/ResEarnCampaignShopItems.csv | Bin 630 -> 0 bytes titles/sao/data/ResEarnCampaignShops.csv | Bin 816 -> 0 bytes titles/sao/data/RewardTable.csv | Bin 323785 -> 0 bytes titles/sao/data/Title.csv | Bin 16225 -> 0 bytes titles/sao/data/YuiMedalShopItems.csv | Bin 41168 -> 0 bytes titles/sao/frontend.py | 264 + titles/sao/handlers/base.py | 6277 ++++---- titles/sao/handlers/helpers.py | 12321 +++++++++++++++- titles/sao/index.py | 83 +- titles/sao/read.py | 400 +- titles/sao/schema/item.py | 429 +- titles/sao/schema/profile.py | 264 +- titles/sao/schema/static.py | 924 +- titles/sao/templates/sao_index.jinja | 182 + 170 files changed, 21389 insertions(+), 4353 deletions(-) create mode 100644 core/data/alembic/versions/a616fd164e40_sao_backport.py create mode 100644 titles/sao/data/1/AcLoginBonuses.csv create mode 100644 titles/sao/data/1/AdBanners.csv create mode 100644 titles/sao/data/1/AppointLeaderEffect.csv create mode 100644 titles/sao/data/1/AppointLeaderEffectType.csv create mode 100644 titles/sao/data/1/AppointLeaderParam.csv create mode 100644 titles/sao/data/1/Awakening.csv create mode 100644 titles/sao/data/1/BattleCamera.csv create mode 100644 titles/sao/data/1/BeginnerMissionConditions.csv create mode 100644 titles/sao/data/1/BeginnerMissionRewards.csv create mode 100644 titles/sao/data/1/BeginnerMissionSeatConditions.csv create mode 100644 titles/sao/data/1/BeginnerMissionSeatRewards.csv create mode 100644 titles/sao/data/1/BeginnerMissions.csv create mode 100644 titles/sao/data/1/BnidSerialCodeRewards.csv create mode 100644 titles/sao/data/1/BnidSerialCodes.csv create mode 100644 titles/sao/data/1/BossAttack.csv create mode 100644 titles/sao/data/1/BuffItem.csv create mode 100644 titles/sao/data/1/BuyTicket.csv create mode 100644 titles/sao/data/1/Cap.csv create mode 100644 titles/sao/data/1/Chara.csv create mode 100644 titles/sao/data/1/CharaAttack.csv create mode 100644 titles/sao/data/1/CharaComment.csv create mode 100644 titles/sao/data/1/CharaFriendlyRank.csv create mode 100644 titles/sao/data/1/ChatEventStory.csv create mode 100644 titles/sao/data/1/ChatMainStory.csv create mode 100644 titles/sao/data/1/ChatSideStory.csv create mode 100644 titles/sao/data/1/ComebackEvent.csv create mode 100644 titles/sao/data/1/CompositionEvent.csv create mode 100644 titles/sao/data/1/CompositionParam.csv create mode 100644 titles/sao/data/1/CostumeType.csv create mode 100644 titles/sao/data/1/DebugValidator.csv create mode 100644 titles/sao/data/1/DefragMatchAIPattern.csv create mode 100644 titles/sao/data/1/DefragMatchBossTable.csv create mode 100644 titles/sao/data/1/DefragMatchChat.csv create mode 100644 titles/sao/data/1/DefragMatchCpuSupportLogs.csv create mode 100644 titles/sao/data/1/DefragMatchCpuUnits.csv create mode 100644 titles/sao/data/1/DefragMatchGrade.csv create mode 100644 titles/sao/data/1/DefragMatchPeriodBonuses.csv create mode 100644 titles/sao/data/1/DefragMatchQuest.csv create mode 100644 titles/sao/data/1/DefragMatchRandomBonusConditions.csv create mode 100644 titles/sao/data/1/DefragMatchRandomBonusTables.csv create mode 100644 titles/sao/data/1/DefragMatchRareDrops.csv create mode 100644 titles/sao/data/1/DefragMatchSeed.csv create mode 100644 titles/sao/data/1/DefragMatchSpecialEffects.csv create mode 100644 titles/sao/data/1/DefragMatchs.csv create mode 100644 titles/sao/data/1/Enemy.csv create mode 100644 titles/sao/data/1/EnemyKind.csv create mode 100644 titles/sao/data/1/EnemySet.csv create mode 100644 titles/sao/data/1/Episode.csv create mode 100644 titles/sao/data/1/EpisodeAppends.csv create mode 100644 titles/sao/data/1/EpisodeChapter.csv create mode 100644 titles/sao/data/1/EpisodePart.csv rename titles/sao/data/{ => 1}/Equipment.csv (86%) create mode 100644 titles/sao/data/1/EquipmentLevel.csv create mode 100644 titles/sao/data/1/Event.csv create mode 100644 titles/sao/data/1/EventItems.csv create mode 100644 titles/sao/data/1/EventMonsters.csv create mode 100644 titles/sao/data/1/EventScenes.csv create mode 100644 titles/sao/data/1/ExBonusCondition.csv create mode 100644 titles/sao/data/1/ExBonusTable.csv create mode 100644 titles/sao/data/1/ExTowerQuests.csv create mode 100644 titles/sao/data/1/ExTowers.csv create mode 100644 titles/sao/data/1/FirstTicketPurchaseCampaigns.csv create mode 100644 titles/sao/data/1/FixedFormChat.csv create mode 100644 titles/sao/data/1/FixedFormChatText.csv create mode 100644 titles/sao/data/1/Fragment.csv create mode 100644 titles/sao/data/1/GamePlayPrice.csv create mode 100644 titles/sao/data/1/GashaFreeCampaigns.csv create mode 100644 titles/sao/data/1/GashaMedalBonuses.csv create mode 100644 titles/sao/data/1/GashaMedalSettings.csv create mode 100644 titles/sao/data/1/GashaMedalShopItems.csv rename titles/sao/data/{ => 1}/GashaMedalShops.csv (65%) create mode 100644 titles/sao/data/1/GashaMedalTypes.csv create mode 100644 titles/sao/data/1/GashaMedals.csv create mode 100644 titles/sao/data/1/GenericCampaignPeriods.csv create mode 100644 titles/sao/data/1/GimmickAttack.csv rename titles/sao/data/{ => 1}/HeroLog.csv (94%) create mode 100644 titles/sao/data/1/HeroLogLevel.csv create mode 100644 titles/sao/data/1/HeroLogRole.csv create mode 100644 titles/sao/data/1/HeroLogTrustRank.csv rename titles/sao/data/{ => 1}/Item.csv (79%) create mode 100644 titles/sao/data/1/ItemType.csv create mode 100644 titles/sao/data/1/LinkedSiteRegCampaignRewards.csv create mode 100644 titles/sao/data/1/LinkedSiteRegCampaigns.csv create mode 100644 titles/sao/data/1/MenuDisplayEnemy.csv create mode 100644 titles/sao/data/1/Mission.csv create mode 100644 titles/sao/data/1/MissionClearText.csv create mode 100644 titles/sao/data/1/MissionDifficulty.csv create mode 100644 titles/sao/data/1/MissionTable.csv create mode 100644 titles/sao/data/1/MonsterAction.csv create mode 100644 titles/sao/data/1/MonsterAttack.csv create mode 100644 titles/sao/data/1/NavigatorChara.csv create mode 100644 titles/sao/data/1/NavigatorComment.csv create mode 100644 titles/sao/data/1/PayingYuiMedalBonuses.csv create mode 100644 titles/sao/data/1/PlayCampaignRewards.csv create mode 100644 titles/sao/data/1/PlayCampaigns.csv create mode 100644 titles/sao/data/1/PlayerRank.csv create mode 100644 titles/sao/data/1/PlayerTraceTable.csv create mode 100644 titles/sao/data/1/Property.csv create mode 100644 titles/sao/data/1/PropertyTable.csv create mode 100644 titles/sao/data/1/PropertyType.csv create mode 100644 titles/sao/data/1/QuestDropBoostCampaigns.csv create mode 100644 titles/sao/data/1/QuestEpisodeAppendRewards.csv create mode 100644 titles/sao/data/1/QuestExistUnit.csv create mode 100644 titles/sao/data/1/QuestParam.csv create mode 100644 titles/sao/data/1/QuestRareDrop.csv rename titles/sao/data/{ => 1}/QuestScene.csv (89%) create mode 100644 titles/sao/data/1/QuestSpecialRareDropSettings.csv create mode 100644 titles/sao/data/1/QuestSpecialRareDrops.csv create mode 100644 titles/sao/data/1/QuestTutorial.csv create mode 100644 titles/sao/data/1/Rarity.csv create mode 100644 titles/sao/data/1/ResEarnCampaignShopItems.csv create mode 100644 titles/sao/data/1/ResEarnCampaignShops.csv create mode 100644 titles/sao/data/1/RewardSet.csv create mode 100644 titles/sao/data/1/RewardTable.csv create mode 100644 titles/sao/data/1/SideQuest.csv create mode 100644 titles/sao/data/1/Skill.csv create mode 100644 titles/sao/data/1/SkillCutInTextIndex.csv create mode 100644 titles/sao/data/1/SkillCutin.csv create mode 100644 titles/sao/data/1/SkillLevel.csv create mode 100644 titles/sao/data/1/SkillTable.csv create mode 100644 titles/sao/data/1/SoundBgm.csv create mode 100644 titles/sao/data/1/SoundSe.csv create mode 100644 titles/sao/data/1/SoundUi.csv create mode 100644 titles/sao/data/1/SoundVoice.csv create mode 100644 titles/sao/data/1/Sound_SkillCutinVoice.csv create mode 100644 titles/sao/data/1/SubStateMonster.csv rename titles/sao/data/{ => 1}/SupportLog.csv (95%) create mode 100644 titles/sao/data/1/SupportLogType.csv create mode 100644 titles/sao/data/1/SynchroSkill.csv create mode 100644 titles/sao/data/1/Tips.csv create mode 100644 titles/sao/data/1/Title.csv create mode 100644 titles/sao/data/1/TreasureHuntTaskText.csv create mode 100644 titles/sao/data/1/TrialTower.csv create mode 100644 titles/sao/data/1/UnanalyzedLogGrade.csv create mode 100644 titles/sao/data/1/Unit.csv create mode 100644 titles/sao/data/1/UnitCollision.csv create mode 100644 titles/sao/data/1/UnitGimmick.csv create mode 100644 titles/sao/data/1/UnitPower.csv create mode 100644 titles/sao/data/1/WeaponType.csv create mode 100644 titles/sao/data/1/YuiMedalBonus.csv create mode 100644 titles/sao/data/1/YuiMedalBonusCondition.csv create mode 100644 titles/sao/data/1/YuiMedalGetCondition.csv create mode 100644 titles/sao/data/1/YuiMedalShopItems.csv rename titles/sao/data/{ => 1}/YuiMedalShops.csv (60%) delete mode 100644 titles/sao/data/EquipmentLevel.csv delete mode 100644 titles/sao/data/GashaMedalShopItems.csv delete mode 100644 titles/sao/data/HeroLogLevel.csv delete mode 100644 titles/sao/data/PlayerRank.csv delete mode 100644 titles/sao/data/RareDropTable.csv delete mode 100644 titles/sao/data/ResEarnCampaignShopItems.csv delete mode 100644 titles/sao/data/ResEarnCampaignShops.csv delete mode 100644 titles/sao/data/RewardTable.csv delete mode 100644 titles/sao/data/Title.csv delete mode 100644 titles/sao/data/YuiMedalShopItems.csv create mode 100644 titles/sao/frontend.py create mode 100644 titles/sao/templates/sao_index.jinja diff --git a/core/data/alembic/versions/a616fd164e40_sao_backport.py b/core/data/alembic/versions/a616fd164e40_sao_backport.py new file mode 100644 index 0000000..c4b885b --- /dev/null +++ b/core/data/alembic/versions/a616fd164e40_sao_backport.py @@ -0,0 +1,723 @@ +"""sao_backport + +Revision ID: a616fd164e40 +Revises: 48f4acc43a7e +Create Date: 2024-06-24 20:28:34.471282 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = 'a616fd164e40' +down_revision = '48f4acc43a7e' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index('sao_static_quest_uk', table_name='sao_static_quest') + op.drop_column('sao_static_quest', 'questSceneId') + op.drop_column('sao_static_quest', 'name') + op.drop_column('sao_static_quest', 'sortNo') + op.drop_column('sao_static_quest', 'id') + op.drop_column('sao_static_quest', 'enabled') + op.add_column('sao_static_quest', sa.Column('QuestSceneId', sa.BIGINT(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('SortNo', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('Tutorial', sa.BOOLEAN(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('ColRate', sa.DECIMAL(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('LimitDefault', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('LimitResurrection', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('RewardTableSubId', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('PlayerTraceTableSubId', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('SuccessPlayerExp', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('FailedPlayerExp', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('PairExpRate', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('TrioExpRate', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('SingleRewardVp', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('PairRewardVp', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('TrioRewardVp', sa.INTEGER(), nullable=False)) + op.create_primary_key(None, "sao_static_quest", ['QuestSceneId']) + op.drop_column('sao_static_quest', 'version') + op.create_table('sao_player_resource_card', + sa.Column('id', sa.BIGINT(), nullable=False), + sa.Column('user', sa.INTEGER(), nullable=False), + sa.Column('common_reward_type', sa.INTEGER(), nullable=False), + sa.Column('common_reward_id', sa.INTEGER(), nullable=False), + sa.Column('holographic_flag', sa.BOOLEAN(), server_default='0', nullable=False), + sa.Column('serial', sa.VARCHAR(length=20), nullable=True), + sa.ForeignKeyConstraint(['user'], ['aime_user.id'], onupdate='cascade', ondelete='cascade'), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('serial'), + mysql_charset='utf8mb4' + ) + op.create_table('sao_player_tutorial', + sa.Column('id', sa.BIGINT(), nullable=False), + sa.Column('user', sa.INTEGER(), nullable=False), + sa.Column('tutorial_byte', sa.INTEGER(), nullable=False), + sa.ForeignKeyConstraint(['user'], ['aime_user.id'], onupdate='cascade', ondelete='cascade'), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('user', 'tutorial_byte', name='sao_player_tutorial_uk'), + mysql_charset='utf8mb4' + ) + op.create_table('sao_static_episode', + sa.Column('EpisodeId', sa.BIGINT(), nullable=False), + sa.Column('EpisodeChapterId', sa.INTEGER(), nullable=False), + sa.Column('ReleaseEpisodeId', sa.INTEGER(), nullable=False), + sa.Column('Title', sa.VARCHAR(length=255), nullable=False), + sa.Column('CommentSummary', sa.VARCHAR(length=255), nullable=False), + sa.Column('ExBonusTableSubId', sa.INTEGER(), nullable=False), + sa.Column('QuestSceneId', sa.BIGINT(), nullable=True), + sa.ForeignKeyConstraint(['QuestSceneId'], ['sao_static_quest.QuestSceneId'], onupdate='cascade', ondelete='cascade'), + sa.PrimaryKeyConstraint('EpisodeId'), + mysql_charset='utf8mb4' + ) + op.create_table('sao_static_ex_bonus', + sa.Column('ExBonusTableId', sa.BIGINT(), nullable=False), + sa.Column('ExBonusTableSubId', sa.INTEGER(), nullable=False), + sa.Column('ExBonusConditionId', sa.INTEGER(), nullable=False), + sa.Column('ConditionValue1', sa.INTEGER(), nullable=False), + sa.Column('ConditionValue2', sa.INTEGER(), nullable=False), + sa.Column('CommonRewardType', sa.INTEGER(), nullable=False), + sa.Column('CommonRewardId', sa.INTEGER(), nullable=False), + sa.Column('CommonRewardNum', sa.INTEGER(), nullable=False), + sa.Column('Strength', sa.INTEGER(), nullable=False), + sa.Column('Property1PropertyId', sa.BIGINT(), nullable=False), + sa.Column('Property1Value1', sa.INTEGER(), nullable=False), + sa.Column('Property1Value2', sa.INTEGER(), nullable=False), + sa.Column('Property2PropertyId', sa.BIGINT(), nullable=False), + sa.Column('Property2Value1', sa.INTEGER(), nullable=False), + sa.Column('Property2Value2', sa.INTEGER(), nullable=False), + sa.Column('Property3PropertyId', sa.BIGINT(), nullable=False), + sa.Column('Property3Value1', sa.INTEGER(), nullable=False), + sa.Column('Property3Value2', sa.INTEGER(), nullable=False), + sa.Column('Property4PropertyId', sa.BIGINT(), nullable=False), + sa.Column('Property4Value1', sa.INTEGER(), nullable=False), + sa.Column('Property4Value2', sa.INTEGER(), nullable=False), + sa.ForeignKeyConstraint(['Property1PropertyId'], ['sao_static_property.PropertyId'], onupdate='cascade', ondelete='cascade'), + sa.ForeignKeyConstraint(['Property2PropertyId'], ['sao_static_property.PropertyId'], onupdate='cascade', ondelete='cascade'), + sa.ForeignKeyConstraint(['Property3PropertyId'], ['sao_static_property.PropertyId'], onupdate='cascade', ondelete='cascade'), + sa.ForeignKeyConstraint(['Property4PropertyId'], ['sao_static_property.PropertyId'], onupdate='cascade', ondelete='cascade'), + sa.PrimaryKeyConstraint('ExBonusTableId'), + mysql_charset='utf8mb4' + ) + op.create_table('sao_static_ex_tower', + sa.Column('ExTowerQuestId', sa.BIGINT(), nullable=False), + sa.Column('ExTowerId', sa.INTEGER(), nullable=False), + sa.Column('ReleaseExTowerQuestId', sa.INTEGER(), nullable=False), + sa.Column('Title', sa.VARCHAR(length=255), nullable=False), + sa.Column('Title_en', sa.VARCHAR(length=255), nullable=True), + sa.Column('ExBonusTableSubId', sa.INTEGER(), nullable=False), + sa.Column('QuestSceneId', sa.BIGINT(), nullable=False), + sa.ForeignKeyConstraint(['QuestSceneId'], ['sao_static_quest.QuestSceneId'], onupdate='cascade', ondelete='cascade'), + sa.PrimaryKeyConstraint('ExTowerQuestId'), + mysql_charset='utf8mb4' + ) + op.create_table('sao_static_side_quest', + sa.Column('SideQuestId', sa.BIGINT(), nullable=False), + sa.Column('DisplayName', sa.VARCHAR(length=255), nullable=False), + sa.Column('DisplayName_en', sa.VARCHAR(length=255), nullable=True), + sa.Column('EpisodeNum', sa.INTEGER(), nullable=False), + sa.Column('ExBonusTableSubId', sa.INTEGER(), nullable=False), + sa.Column('QuestSceneId', sa.BIGINT(), nullable=False), + sa.ForeignKeyConstraint(['QuestSceneId'], ['sao_static_quest.QuestSceneId'], onupdate='cascade', ondelete='cascade'), + sa.PrimaryKeyConstraint('SideQuestId'), + sa.UniqueConstraint('SideQuestId'), + mysql_charset='utf8mb4' + ) + op.create_table('sao_static_skill_table', + sa.Column('SkillTableId', sa.BIGINT(), nullable=False), + sa.Column('SkillId', sa.BIGINT(), nullable=False), + sa.Column('SkillTableSubId', sa.INTEGER(), nullable=False), + sa.Column('LevelObtained', sa.INTEGER(), nullable=False), + sa.Column('AwakeningId', sa.INTEGER(), nullable=False), + sa.ForeignKeyConstraint(['SkillId'], ['sao_static_skill.SkillId'], onupdate='cascade', ondelete='cascade'), + sa.PrimaryKeyConstraint('SkillTableId'), + mysql_charset='utf8mb4' + ) + op.create_table('sao_static_tower', + sa.Column('TowerId', sa.BIGINT(), nullable=False), + sa.Column('ReleaseTowerId', sa.INTEGER(), nullable=False), + sa.Column('ExBonusTableSubId', sa.INTEGER(), nullable=False), + sa.Column('QuestSceneId', sa.BIGINT(), nullable=False), + sa.ForeignKeyConstraint(['QuestSceneId'], ['sao_static_quest.QuestSceneId'], onupdate='cascade', ondelete='cascade'), + sa.PrimaryKeyConstraint('TowerId'), + mysql_charset='utf8mb4' + ) + op.create_table('sao_player_ex_bonus', + sa.Column('id', sa.BIGINT(), nullable=False), + sa.Column('user', sa.INTEGER(), nullable=False), + sa.Column('quest_scene_id', sa.BIGINT(), nullable=False), + sa.Column('ex_bonus_table_id', sa.BIGINT(), nullable=False), + sa.Column('quest_clear_flag', sa.BOOLEAN(), server_default='0', nullable=False), + sa.ForeignKeyConstraint(['ex_bonus_table_id'], ['sao_static_ex_bonus.ExBonusTableId'], onupdate='cascade', ondelete='cascade'), + sa.ForeignKeyConstraint(['quest_scene_id'], ['sao_static_quest.QuestSceneId'], onupdate='cascade', ondelete='cascade'), + sa.ForeignKeyConstraint(['user'], ['aime_user.id'], onupdate='cascade', ondelete='cascade'), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('user', 'quest_scene_id', 'ex_bonus_table_id', name='sao_player_ex_bonus_uk'), + mysql_charset='utf8mb4' + ) + op.alter_column('sao_end_sessions', 'play_date', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('now()'), + existing_nullable=False) + op.alter_column("sao_equipment_data", "equipment_id", existing_type=sa.Integer(), type_=sa.BIGINT()) + op.add_column('sao_equipment_data', sa.Column('is_shop_purchase', sa.BOOLEAN(), server_default='0', nullable=False)) + op.add_column('sao_equipment_data', sa.Column('is_protect', sa.BOOLEAN(), server_default='0', nullable=False)) + op.add_column('sao_equipment_data', sa.Column('property1_property_id', sa.BIGINT(), server_default='2', nullable=False)) + op.add_column('sao_equipment_data', sa.Column('property1_value1', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_equipment_data', sa.Column('property1_value2', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_equipment_data', sa.Column('property2_property_id', sa.BIGINT(), server_default='2', nullable=False)) + op.add_column('sao_equipment_data', sa.Column('property2_value1', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_equipment_data', sa.Column('property2_value2', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_equipment_data', sa.Column('property3_property_id', sa.BIGINT(), server_default='2', nullable=False)) + op.add_column('sao_equipment_data', sa.Column('property3_value1', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_equipment_data', sa.Column('property3_value2', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_equipment_data', sa.Column('property4_property_id', sa.BIGINT(), server_default='2', nullable=False)) + op.add_column('sao_equipment_data', sa.Column('property4_value1', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_equipment_data', sa.Column('property4_value2', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_equipment_data', sa.Column('converted_card_num', sa.INTEGER(), server_default='0', nullable=False)) + op.alter_column('sao_equipment_data', 'get_date', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('now()'), + existing_nullable=False) + op.drop_column('sao_static_equipment_list', 'id') + op.drop_column('sao_static_equipment_list', 'name') + op.drop_column('sao_static_equipment_list', 'flavorText') + op.drop_column('sao_static_equipment_list', 'equipmentId') + op.drop_column('sao_static_equipment_list', 'rarity') + op.drop_column('sao_static_equipment_list', 'enabled') + op.drop_column('sao_static_equipment_list', 'weaponTypeId') + op.drop_column('sao_static_equipment_list', 'equipmentType') + op.add_column('sao_static_equipment_list', sa.Column('EquipmentId', sa.BIGINT(), nullable=False)) + op.add_column('sao_static_equipment_list', sa.Column('EquipmentType', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_equipment_list', sa.Column('WeaponTypeId', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_equipment_list', sa.Column('Name', sa.VARCHAR(length=255), nullable=False)) + op.add_column('sao_static_equipment_list', sa.Column('Name_en', sa.VARCHAR(length=255), nullable=True)) + op.add_column('sao_static_equipment_list', sa.Column('Rarity', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_equipment_list', sa.Column('Power', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_equipment_list', sa.Column('StrengthIncrement', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_equipment_list', sa.Column('SkillCondition', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_equipment_list', sa.Column('Property1PropertyId', sa.BIGINT(), nullable=False)) + op.add_column('sao_static_equipment_list', sa.Column('Property1Value1', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_equipment_list', sa.Column('Property1Value2', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_equipment_list', sa.Column('Property2PropertyId', sa.BIGINT(), nullable=False)) + op.add_column('sao_static_equipment_list', sa.Column('Property2Value1', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_equipment_list', sa.Column('Property2Value2', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_equipment_list', sa.Column('Property3PropertyId', sa.BIGINT(), nullable=False)) + op.add_column('sao_static_equipment_list', sa.Column('Property3Value1', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_equipment_list', sa.Column('Property3Value2', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_equipment_list', sa.Column('Property4PropertyId', sa.BIGINT(), nullable=False)) + op.add_column('sao_static_equipment_list', sa.Column('Property4Value1', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_equipment_list', sa.Column('Property4Value2', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_equipment_list', sa.Column('SalePrice', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_equipment_list', sa.Column('CompositionExp', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_equipment_list', sa.Column('AwakeningExp', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_equipment_list', sa.Column('FlavorText', sa.VARCHAR(length=255), nullable=False)) + op.add_column('sao_static_equipment_list', sa.Column('FlavorText_en', sa.VARCHAR(length=255), nullable=True)) + op.create_primary_key(None, "sao_static_equipment_list", ['EquipmentId']) + op.drop_index('sao_static_equipment_list_uk', table_name='sao_static_equipment_list') + op.create_foreign_key(None, 'sao_static_equipment_list', 'sao_static_property', ['Property1PropertyId'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_static_equipment_list', 'sao_static_property', ['Property2PropertyId'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_static_equipment_list', 'sao_static_property', ['Property3PropertyId'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_static_equipment_list', 'sao_static_property', ['Property4PropertyId'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.drop_column('sao_static_equipment_list', 'version') + op.create_foreign_key(None, 'sao_equipment_data', 'sao_static_equipment_list', ['equipment_id'], ['EquipmentId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_equipment_data', 'sao_static_property', ['property1_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_equipment_data', 'sao_static_property', ['property4_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_equipment_data', 'sao_static_property', ['property2_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_equipment_data', 'sao_static_property', ['property3_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.add_column('sao_hero_log_data', sa.Column('hero_log_id', sa.BIGINT(), nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('max_level_extend_num', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('is_awakenable', sa.BOOLEAN(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('awakening_stage', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('awakening_exp', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('is_shop_purchase', sa.BOOLEAN(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('is_protect', sa.BOOLEAN(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property1_property_id', sa.BIGINT(), server_default='2', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property1_value1', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property1_value2', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property2_property_id', sa.BIGINT(), server_default='2', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property2_value1', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property2_value2', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property3_property_id', sa.BIGINT(), server_default='2', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property3_value1', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property3_value2', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property4_property_id', sa.BIGINT(), server_default='2', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property4_value1', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property4_value2', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('converted_card_num', sa.INTEGER(), server_default='0', nullable=False)) + op.alter_column('sao_hero_log_data', 'main_weapon', + existing_type=mysql.INTEGER(display_width=11), + nullable=True) + op.alter_column('sao_hero_log_data', 'sub_equipment', + existing_type=mysql.INTEGER(display_width=11), + nullable=True) + op.alter_column('sao_hero_log_data', 'skill_slot1_skill_id', + existing_type=mysql.INTEGER(display_width=11), + type_=sa.BIGINT(), + nullable=True) + op.alter_column('sao_hero_log_data', 'skill_slot2_skill_id', + existing_type=mysql.INTEGER(display_width=11), + type_=sa.BIGINT(), + nullable=True) + op.alter_column('sao_hero_log_data', 'skill_slot3_skill_id', + existing_type=mysql.INTEGER(display_width=11), + type_=sa.BIGINT(), + nullable=True) + op.alter_column('sao_hero_log_data', 'skill_slot4_skill_id', + existing_type=mysql.INTEGER(display_width=11), + type_=sa.BIGINT(), + nullable=True) + op.alter_column('sao_hero_log_data', 'skill_slot5_skill_id', + existing_type=mysql.INTEGER(display_width=11), + type_=sa.BIGINT(), + nullable=True) + op.alter_column('sao_hero_log_data', 'get_date', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('now()'), + existing_nullable=False) + op.drop_constraint('sao_hero_log_data_ibfk_1', 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint('sao_hero_log_data_uk', 'sao_hero_log_data', type_='unique') + op.create_unique_constraint('sao_hero_log_data_uk', 'sao_hero_log_data', ['user', 'hero_log_id']) + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_property', ['property2_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_skill', ['skill_slot2_skill_id'], ['SkillId'], onupdate='set null', ondelete='set null') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_property', ['property1_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_skill', ['skill_slot1_skill_id'], ['SkillId'], onupdate='set null', ondelete='set null') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_equipment_data', ['main_weapon'], ['id'], onupdate='set null', ondelete='set null') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_property', ['property4_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_skill', ['skill_slot4_skill_id'], ['SkillId'], onupdate='set null', ondelete='set null') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_skill', ['skill_slot3_skill_id'], ['SkillId'], onupdate='set null', ondelete='set null') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_property', ['property3_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_equipment_data', ['sub_equipment'], ['id'], onupdate='set null', ondelete='set null') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_skill', ['skill_slot5_skill_id'], ['SkillId'], onupdate='set null', ondelete='set null') + op.drop_index('sao_static_hero_list_uk', table_name='sao_static_hero_list') + op.drop_column('sao_static_hero_list', 'name') + op.drop_column('sao_static_hero_list', 'skillTableSubId') + op.drop_column('sao_static_hero_list', 'awakeningExp') + op.drop_column('sao_static_hero_list', 'flavorText') + op.drop_column('sao_static_hero_list', 'heroLogId') + op.drop_column('sao_static_hero_list', 'id') + op.drop_column('sao_static_hero_list', 'rarity') + op.drop_column('sao_static_hero_list', 'version') + op.drop_column('sao_static_hero_list', 'nickname') + op.add_column('sao_static_hero_list', sa.Column('HeroLogId', sa.BIGINT(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('CharaId', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('Name', sa.VARCHAR(length=255), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('Nickname', sa.VARCHAR(length=255), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('Name_en', sa.VARCHAR(length=255), nullable=True)) + op.add_column('sao_static_hero_list', sa.Column('Nickname_en', sa.VARCHAR(length=255), nullable=True)) + op.add_column('sao_static_hero_list', sa.Column('Rarity', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('WeaponTypeId', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('HeroLogRoleId', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('CostumeTypeId', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('UnitId', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('DefaultEquipmentId1', sa.BIGINT(), nullable=True)) + op.add_column('sao_static_hero_list', sa.Column('DefaultEquipmentId2', sa.BIGINT(), nullable=True)) + op.add_column('sao_static_hero_list', sa.Column('SkillTableSubId', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('HpMin', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('HpMax', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('StrMin', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('StrMax', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('VitMin', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('VitMax', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('IntMin', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('IntMax', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('Property1PropertyId', sa.BIGINT(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('Property1Value1', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('Property1Value2', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('Property2PropertyId', sa.BIGINT(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('Property2Value1', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('Property2Value2', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('Property3PropertyId', sa.BIGINT(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('Property3Value1', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('Property3Value2', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('Property4PropertyId', sa.BIGINT(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('Property4Value1', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('Property4Value2', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('FlavorText', sa.VARCHAR(length=255), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('FlavorText_en', sa.VARCHAR(length=255), nullable=True)) + op.add_column('sao_static_hero_list', sa.Column('SalePrice', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('CompositionExp', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('AwakeningExp', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('Slot4UnlockLevel', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('Slot5UnlockLevel', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('CollectionEmptyFrameDisplayFlag', sa.BOOLEAN(), nullable=False)) + op.create_foreign_key(None, 'sao_static_hero_list', 'sao_static_equipment_list', ['DefaultEquipmentId1'], ['EquipmentId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_static_hero_list', 'sao_static_equipment_list', ['DefaultEquipmentId2'], ['EquipmentId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_static_hero_list', 'sao_static_property', ['Property2PropertyId'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_static_hero_list', 'sao_static_property', ['Property3PropertyId'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_static_hero_list', 'sao_static_property', ['Property4PropertyId'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_static_hero_list', 'sao_static_property', ['Property1PropertyId'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.drop_column('sao_static_hero_list', 'enabled') + op.create_primary_key(None, "sao_static_hero_list", ["HeroLogId"]) + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_hero_list', ['hero_log_id'], ['HeroLogId'], onupdate='cascade', ondelete='cascade') + op.drop_column('sao_hero_log_data', 'user_hero_log_id') + op.create_foreign_key(None, 'sao_hero_log_data', 'aime_user', ['user'], ['id'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_hero_party', 'sao_hero_log_data', ['user_hero_log_id_3'], ['id'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_hero_party', 'sao_hero_log_data', ['user_hero_log_id_1'], ['id'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_hero_party', 'sao_hero_log_data', ['user_hero_log_id_2'], ['id'], onupdate='cascade', ondelete='cascade') + op.create_table('sao_player_hero_card', + sa.Column('id', sa.BIGINT(), nullable=False), + sa.Column('user', sa.INTEGER(), nullable=False), + sa.Column('user_hero_id', sa.INTEGER(), nullable=False), + sa.Column('holographic_flag', sa.BOOLEAN(), server_default='0', nullable=False), + sa.Column('serial', sa.VARCHAR(length=20), nullable=True), + sa.ForeignKeyConstraint(['user'], ['aime_user.id'], onupdate='cascade', ondelete='cascade'), + sa.ForeignKeyConstraint(['user_hero_id'], ['sao_hero_log_data.id'], onupdate='cascade', ondelete='cascade'), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('serial'), + mysql_charset='utf8mb4' + ) + op.alter_column('sao_item_data', 'get_date', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('now()'), + existing_nullable=False) + op.alter_column('sao_play_sessions', 'play_date', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('now()'), + existing_nullable=False) + op.add_column('sao_player_quest', sa.Column('quest_type', sa.INTEGER(), server_default='1', nullable=False)) + op.add_column('sao_player_quest', sa.Column('quest_scene_id', sa.BIGINT(), nullable=False)) + op.alter_column('sao_player_quest', 'play_date', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('now()'), + existing_nullable=False) + op.drop_constraint('sao_player_quest_ibfk_1', 'sao_player_quest', type_='foreignkey') + op.drop_constraint('sao_player_quest_uk', 'sao_player_quest', type_='unique') + op.create_unique_constraint('sao_player_quest_uk', 'sao_player_quest', ['user', 'quest_scene_id']) + op.create_foreign_key(None, "sao_player_quest", "aime_user", ['user'], ['id']) + op.create_foreign_key(None, 'sao_player_quest', 'sao_static_quest', ['quest_scene_id'], ['QuestSceneId'], onupdate='cascade', ondelete='cascade') + op.drop_column('sao_player_quest', 'episode_id') + op.add_column('sao_profile', sa.Column('my_shop', sa.INTEGER(), nullable=True)) + op.add_column('sao_profile', sa.Column('fav_hero', sa.INTEGER(), nullable=True)) + op.add_column('sao_profile', sa.Column('when_register', sa.TIMESTAMP(), server_default=sa.text('now()'), nullable=True)) + op.add_column('sao_profile', sa.Column('last_login_date', sa.TIMESTAMP(), nullable=True)) + op.add_column('sao_profile', sa.Column('last_yui_medal_date', sa.TIMESTAMP(), nullable=True)) + op.add_column('sao_profile', sa.Column('last_bonus_yui_medal_date', sa.TIMESTAMP(), nullable=True)) + op.add_column('sao_profile', sa.Column('last_comeback_date', sa.TIMESTAMP(), nullable=True)) + op.add_column('sao_profile', sa.Column('last_login_bonus_date', sa.TIMESTAMP(), nullable=True)) + op.add_column('sao_profile', sa.Column('ad_confirm_date', sa.TIMESTAMP(), nullable=True)) + op.add_column('sao_profile', sa.Column('login_ct', sa.INTEGER(), server_default='0', nullable=True)) + op.alter_column('sao_profile', 'own_vp', + existing_type=mysql.INTEGER(display_width=11), + server_default='0', + existing_nullable=True) + op.create_foreign_key(None, 'sao_profile', 'sao_hero_log_data', ['fav_hero'], ['id'], onupdate='cascade', ondelete='set null') + op.drop_column('sao_static_item_list', 'name') + op.drop_column('sao_static_item_list', 'itemId') + op.drop_column('sao_static_item_list', 'itemTypeId') + op.drop_column('sao_static_item_list', 'flavorText') + op.drop_column('sao_static_item_list', 'id') + op.drop_column('sao_static_item_list', 'rarity') + op.drop_column('sao_static_item_list', 'enabled') + op.add_column('sao_static_item_list', sa.Column('ItemId', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_item_list', sa.Column('ItemTypeId', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_item_list', sa.Column('Name', sa.VARCHAR(length=255), nullable=False)) + op.add_column('sao_static_item_list', sa.Column('Name_en', sa.VARCHAR(length=255), nullable=True)) + op.add_column('sao_static_item_list', sa.Column('Rarity', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_item_list', sa.Column('Value', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_item_list', sa.Column('PropertyId', sa.BIGINT(), nullable=False)) + op.add_column('sao_static_item_list', sa.Column('PropertyValue1Min', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_item_list', sa.Column('PropertyValue1Max', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_item_list', sa.Column('PropertyValue2Min', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_item_list', sa.Column('PropertyValue2Max', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_item_list', sa.Column('FlavorText', sa.VARCHAR(length=255), nullable=False)) + op.add_column('sao_static_item_list', sa.Column('FlavorText_en', sa.VARCHAR(length=255), nullable=True)) + op.add_column('sao_static_item_list', sa.Column('SalePrice', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_item_list', sa.Column('ItemIcon', sa.VARCHAR(length=255), nullable=False)) + op.drop_index('sao_static_item_list_uk', table_name='sao_static_item_list') + op.create_foreign_key(None, 'sao_static_item_list', 'sao_static_property', ['PropertyId'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.drop_column('sao_static_item_list', 'version') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint("sao_static_item_list_ibfk_1", 'sao_static_item_list', type_='foreignkey') + op.drop_column('sao_static_item_list', 'ItemIcon') + op.drop_column('sao_static_item_list', 'SalePrice') + op.drop_column('sao_static_item_list', 'FlavorText_en') + op.drop_column('sao_static_item_list', 'FlavorText') + op.drop_column('sao_static_item_list', 'PropertyValue2Max') + op.drop_column('sao_static_item_list', 'PropertyValue2Min') + op.drop_column('sao_static_item_list', 'PropertyValue1Max') + op.drop_column('sao_static_item_list', 'PropertyValue1Min') + op.drop_column('sao_static_item_list', 'PropertyId') + op.drop_column('sao_static_item_list', 'Value') + op.drop_column('sao_static_item_list', 'Rarity') + op.drop_column('sao_static_item_list', 'Name') + op.drop_column('sao_static_item_list', 'ItemTypeId') + op.drop_column('sao_static_item_list', 'ItemId') + op.add_column('sao_static_item_list', sa.Column('version', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) + op.add_column('sao_static_item_list', sa.Column('enabled', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True)) + op.add_column('sao_static_item_list', sa.Column('rarity', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) + op.add_column('sao_static_item_list', sa.Column('id', mysql.INTEGER(display_width=11), autoincrement=True, nullable=False)) + op.add_column('sao_static_item_list', sa.Column('flavorText', mysql.VARCHAR(length=255), nullable=True)) + op.add_column('sao_static_item_list', sa.Column('itemTypeId', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) + op.add_column('sao_static_item_list', sa.Column('itemId', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) + op.add_column('sao_static_item_list', sa.Column('name', mysql.VARCHAR(length=255), nullable=True)) + op.add_column('sao_hero_log_data', sa.Column('user_hero_log_id', mysql.INTEGER(display_width=11), autoincrement=False, nullable=False)) + op.drop_column('sao_static_item_list', 'Name_en') + op.create_index('sao_static_item_list_uk', 'sao_static_item_list', ['version', 'itemId'], unique=True) + op.drop_constraint("sao_hero_log_data_ibfk_1", 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint("sao_hero_log_data_ibfk_2", 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint("sao_hero_log_data_ibfk_3", 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint("sao_hero_log_data_ibfk_4", 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint("sao_hero_log_data_ibfk_5", 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint("sao_hero_log_data_ibfk_6", 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint("sao_hero_log_data_ibfk_7", 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint("sao_hero_log_data_ibfk_8", 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint("sao_hero_log_data_ibfk_9", 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint("sao_hero_log_data_ibfk_10", 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint("sao_hero_log_data_ibfk_11", 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint("sao_hero_log_data_ibfk_12", 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint("sao_hero_log_data_ibfk_13", 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint('sao_hero_log_data_uk', 'sao_hero_log_data', type_='unique') + op.create_foreign_key("sao_hero_log_data_ibfk_1", 'sao_hero_log_data', "aime_user", ['user'], ['id']) + op.drop_column('sao_hero_log_data', 'converted_card_num') + op.drop_column('sao_hero_log_data', 'property4_value2') + op.drop_column('sao_hero_log_data', 'property4_value1') + op.drop_column('sao_hero_log_data', 'property4_property_id') + op.drop_column('sao_hero_log_data', 'property3_value2') + op.drop_column('sao_hero_log_data', 'property3_value1') + op.drop_column('sao_hero_log_data', 'property3_property_id') + op.drop_column('sao_hero_log_data', 'property2_value2') + op.drop_column('sao_hero_log_data', 'property2_value1') + op.drop_column('sao_hero_log_data', 'property2_property_id') + op.drop_column('sao_hero_log_data', 'property1_value2') + op.drop_column('sao_hero_log_data', 'property1_value1') + op.drop_column('sao_hero_log_data', 'property1_property_id') + op.drop_column('sao_hero_log_data', 'is_protect') + op.drop_column('sao_hero_log_data', 'is_shop_purchase') + op.drop_column('sao_hero_log_data', 'awakening_exp') + op.drop_column('sao_hero_log_data', 'awakening_stage') + op.drop_column('sao_hero_log_data', 'is_awakenable') + op.drop_column('sao_hero_log_data', 'max_level_extend_num') + op.drop_column('sao_hero_log_data', 'hero_log_id') + op.alter_column('sao_hero_log_data', 'get_date', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('CURRENT_TIMESTAMP'), + existing_nullable=False) + op.alter_column('sao_hero_log_data', 'skill_slot5_skill_id', + existing_type=mysql.INTEGER(display_width=11), + nullable=False) + op.alter_column('sao_hero_log_data', 'skill_slot4_skill_id', + existing_type=mysql.INTEGER(display_width=11), + nullable=False) + op.alter_column('sao_hero_log_data', 'skill_slot3_skill_id', + existing_type=mysql.INTEGER(display_width=11), + nullable=False) + op.alter_column('sao_hero_log_data', 'skill_slot2_skill_id', + existing_type=mysql.INTEGER(display_width=11), + nullable=False) + op.alter_column('sao_hero_log_data', 'skill_slot1_skill_id', + existing_type=mysql.INTEGER(display_width=11), + nullable=False) + op.alter_column('sao_hero_log_data', 'sub_equipment', + existing_type=mysql.INTEGER(display_width=11), + nullable=False) + op.alter_column('sao_hero_log_data', 'main_weapon', + existing_type=mysql.INTEGER(display_width=11), + nullable=False) + op.create_unique_constraint('sao_hero_log_data_uk', 'sao_hero_log_data', ['user', 'user_hero_log_id']) + op.drop_constraint("sao_static_hero_list_ibfk_1", 'sao_static_hero_list', type_='foreignkey') + op.drop_constraint("sao_static_hero_list_ibfk_2", 'sao_static_hero_list', type_='foreignkey') + op.drop_constraint("sao_static_hero_list_ibfk_3", 'sao_static_hero_list', type_='foreignkey') + op.drop_constraint("sao_static_hero_list_ibfk_4", 'sao_static_hero_list', type_='foreignkey') + op.drop_constraint("sao_static_hero_list_ibfk_5", 'sao_static_hero_list', type_='foreignkey') + op.drop_constraint("sao_static_hero_list_ibfk_6", 'sao_static_hero_list', type_='foreignkey') + op.drop_column('sao_static_hero_list', 'CollectionEmptyFrameDisplayFlag') + op.drop_column('sao_static_hero_list', 'Slot5UnlockLevel') + op.drop_column('sao_static_hero_list', 'Slot4UnlockLevel') + op.drop_column('sao_static_hero_list', 'AwakeningExp') + op.drop_column('sao_static_hero_list', 'CompositionExp') + op.drop_column('sao_static_hero_list', 'SalePrice') + op.drop_column('sao_static_hero_list', 'FlavorText_en') + op.drop_column('sao_static_hero_list', 'FlavorText') + op.drop_column('sao_static_hero_list', 'Property4Value2') + op.drop_column('sao_static_hero_list', 'Property4Value1') + op.drop_column('sao_static_hero_list', 'Property4PropertyId') + op.drop_column('sao_static_hero_list', 'Property3Value2') + op.drop_column('sao_static_hero_list', 'Property3Value1') + op.drop_column('sao_static_hero_list', 'Property3PropertyId') + op.drop_column('sao_static_hero_list', 'Property2Value2') + op.drop_column('sao_static_hero_list', 'Property2Value1') + op.drop_column('sao_static_hero_list', 'Property2PropertyId') + op.drop_column('sao_static_hero_list', 'Property1Value2') + op.drop_column('sao_static_hero_list', 'Property1Value1') + op.drop_column('sao_static_hero_list', 'Property1PropertyId') + op.drop_column('sao_static_hero_list', 'IntMax') + op.drop_column('sao_static_hero_list', 'IntMin') + op.drop_column('sao_static_hero_list', 'VitMax') + op.drop_column('sao_static_hero_list', 'VitMin') + op.drop_column('sao_static_hero_list', 'StrMax') + op.drop_column('sao_static_hero_list', 'StrMin') + op.drop_column('sao_static_hero_list', 'HpMax') + op.drop_column('sao_static_hero_list', 'SkillTableSubId') + op.drop_column('sao_static_hero_list', 'DefaultEquipmentId2') + op.drop_column('sao_static_hero_list', 'DefaultEquipmentId1') + op.drop_column('sao_static_hero_list', 'UnitId') + op.drop_column('sao_static_hero_list', 'CostumeTypeId') + op.drop_column('sao_static_hero_list', 'HeroLogRoleId') + op.drop_column('sao_static_hero_list', 'WeaponTypeId') + op.drop_column('sao_static_hero_list', 'Rarity') + op.drop_column('sao_static_hero_list', 'Nickname_en') + op.drop_column('sao_static_hero_list', 'Name_en') + op.drop_column('sao_static_hero_list', 'Nickname') + op.drop_column('sao_static_hero_list', 'Name') + op.drop_column('sao_static_hero_list', 'CharaId') + op.drop_column('sao_static_hero_list', 'HeroLogId') + op.add_column('sao_static_hero_list', sa.Column('nickname', mysql.VARCHAR(length=255), nullable=True)) + op.add_column('sao_static_hero_list', sa.Column('version', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) + op.add_column('sao_static_hero_list', sa.Column('enabled', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True)) + op.add_column('sao_static_hero_list', sa.Column('rarity', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) + op.add_column('sao_static_hero_list', sa.Column('id', mysql.INTEGER(display_width=11), autoincrement=True, nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('heroLogId', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) + op.add_column('sao_static_hero_list', sa.Column('flavorText', mysql.VARCHAR(length=255), nullable=True)) + op.add_column('sao_static_hero_list', sa.Column('awakeningExp', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) + op.add_column('sao_static_hero_list', sa.Column('skillTableSubId', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) + op.add_column('sao_static_hero_list', sa.Column('name', mysql.VARCHAR(length=255), nullable=True)) + op.drop_column('sao_static_hero_list', 'HpMin') + op.create_index('sao_static_hero_list_uk', 'sao_static_hero_list', ['version', 'heroLogId'], unique=True) + op.drop_constraint("sao_equipment_data_ibfk_2", 'sao_equipment_data', type_='foreignkey') + op.drop_constraint("sao_equipment_data_ibfk_3", 'sao_equipment_data', type_='foreignkey') + op.drop_constraint("sao_equipment_data_ibfk_4", 'sao_equipment_data', type_='foreignkey') + op.drop_constraint("sao_equipment_data_ibfk_5", 'sao_equipment_data', type_='foreignkey') + op.drop_constraint("sao_equipment_data_ibfk_6", 'sao_equipment_data', type_='foreignkey') + op.alter_column('sao_equipment_data', 'get_date', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('CURRENT_TIMESTAMP'), + existing_nullable=False) + op.drop_column('sao_equipment_data', 'converted_card_num') + op.drop_column('sao_equipment_data', 'property4_value2') + op.drop_column('sao_equipment_data', 'property4_value1') + op.drop_column('sao_equipment_data', 'property4_property_id') + op.drop_column('sao_equipment_data', 'property3_value2') + op.drop_column('sao_equipment_data', 'property3_value1') + op.drop_column('sao_equipment_data', 'property3_property_id') + op.drop_column('sao_equipment_data', 'property2_value2') + op.drop_column('sao_equipment_data', 'property2_value1') + op.drop_column('sao_equipment_data', 'property2_property_id') + op.drop_column('sao_equipment_data', 'property1_value2') + op.drop_column('sao_equipment_data', 'property1_value1') + op.drop_column('sao_equipment_data', 'property1_property_id') + op.drop_column('sao_equipment_data', 'is_protect') + op.drop_column('sao_equipment_data', 'is_shop_purchase') + op.drop_constraint("sao_static_equipment_list_ibfk_1", 'sao_static_equipment_list', type_='foreignkey') + op.drop_constraint("sao_static_equipment_list_ibfk_2", 'sao_static_equipment_list', type_='foreignkey') + op.drop_constraint("sao_static_equipment_list_ibfk_3", 'sao_static_equipment_list', type_='foreignkey') + op.drop_constraint("sao_static_equipment_list_ibfk_4", 'sao_static_equipment_list', type_='foreignkey') + op.drop_column('sao_static_equipment_list', 'FlavorText_en') + op.drop_column('sao_static_equipment_list', 'FlavorText') + op.drop_column('sao_static_equipment_list', 'AwakeningExp') + op.drop_column('sao_static_equipment_list', 'CompositionExp') + op.drop_column('sao_static_equipment_list', 'SalePrice') + op.drop_column('sao_static_equipment_list', 'Property4Value2') + op.drop_column('sao_static_equipment_list', 'Property4Value1') + op.drop_column('sao_static_equipment_list', 'Property4PropertyId') + op.drop_column('sao_static_equipment_list', 'Property3Value2') + op.drop_column('sao_static_equipment_list', 'Property3Value1') + op.drop_column('sao_static_equipment_list', 'Property3PropertyId') + op.drop_column('sao_static_equipment_list', 'Property2Value2') + op.drop_column('sao_static_equipment_list', 'Property2Value1') + op.drop_column('sao_static_equipment_list', 'Property2PropertyId') + op.drop_column('sao_static_equipment_list', 'Property1Value2') + op.drop_column('sao_static_equipment_list', 'Property1Value1') + op.drop_column('sao_static_equipment_list', 'Property1PropertyId') + op.drop_column('sao_static_equipment_list', 'SkillCondition') + op.drop_column('sao_static_equipment_list', 'StrengthIncrement') + op.drop_column('sao_static_equipment_list', 'Power') + op.drop_column('sao_static_equipment_list', 'Rarity') + op.drop_column('sao_static_equipment_list', 'Name') + op.drop_column('sao_static_equipment_list', 'WeaponTypeId') + op.drop_column('sao_static_equipment_list', 'EquipmentType') + op.drop_column('sao_static_equipment_list', 'EquipmentId') + op.add_column('sao_static_equipment_list', sa.Column('equipmentType', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) + op.add_column('sao_static_equipment_list', sa.Column('version', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) + op.add_column('sao_static_equipment_list', sa.Column('weaponTypeId', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) + op.add_column('sao_static_equipment_list', sa.Column('enabled', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True)) + op.add_column('sao_static_equipment_list', sa.Column('rarity', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) + op.add_column('sao_static_equipment_list', sa.Column('equipmentId', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) + op.add_column('sao_static_equipment_list', sa.Column('id', mysql.INTEGER(display_width=11), autoincrement=True, nullable=False)) + op.add_column('sao_static_equipment_list', sa.Column('flavorText', mysql.VARCHAR(length=255), nullable=True)) + op.add_column('sao_static_equipment_list', sa.Column('name', mysql.VARCHAR(length=255), nullable=True)) + op.drop_column('sao_static_equipment_list', 'Name_en') + op.create_index('sao_static_equipment_list_uk', 'sao_static_equipment_list', ['version', 'equipmentId'], unique=True) + op.drop_constraint("sao_profile_ibfk_2", 'sao_profile', type_='foreignkey') + op.alter_column('sao_profile', 'own_vp', + existing_type=mysql.INTEGER(display_width=11), + server_default=sa.text("'300'"), + existing_nullable=True) + op.drop_column('sao_profile', 'login_ct') + op.drop_column('sao_profile', 'ad_confirm_date') + op.drop_column('sao_profile', 'last_login_bonus_date') + op.drop_column('sao_profile', 'last_comeback_date') + op.drop_column('sao_profile', 'last_bonus_yui_medal_date') + op.drop_column('sao_profile', 'last_yui_medal_date') + op.drop_column('sao_profile', 'last_login_date') + op.drop_column('sao_profile', 'when_register') + op.drop_column('sao_profile', 'fav_hero') + op.drop_column('sao_profile', 'my_shop') + op.add_column('sao_player_quest', sa.Column('episode_id', mysql.INTEGER(display_width=11), autoincrement=False, nullable=False)) + op.drop_constraint("sao_player_quest_ibfk_2", 'sao_player_quest', type_='foreignkey') + op.drop_constraint("sao_player_quest_ibfk_1", 'sao_player_quest', type_='foreignkey') + op.drop_constraint('sao_player_quest_uk', 'sao_player_quest', type_='unique') + op.create_foreign_key("sao_player_quest_ibfk_1", "sao_player_quest", "aime_user", ['user'], ['id']) + op.create_unique_constraint('sao_player_quest_uk', 'sao_player_quest', ['user', 'episode_id']) + op.alter_column('sao_player_quest', 'play_date', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('CURRENT_TIMESTAMP'), + existing_nullable=False) + op.drop_column('sao_player_quest', 'quest_scene_id') + op.drop_column('sao_player_quest', 'quest_type') + op.alter_column('sao_play_sessions', 'play_date', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('CURRENT_TIMESTAMP'), + existing_nullable=False) + op.alter_column('sao_item_data', 'get_date', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('CURRENT_TIMESTAMP'), + existing_nullable=False) + op.drop_constraint("sao_hero_party_ibfk_2", 'sao_hero_party', type_='foreignkey') + op.drop_constraint("sao_hero_party_ibfk_3", 'sao_hero_party', type_='foreignkey') + op.drop_constraint("sao_hero_party_ibfk_4", 'sao_hero_party', type_='foreignkey') + op.alter_column('sao_end_sessions', 'play_date', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('CURRENT_TIMESTAMP'), + existing_nullable=False) + op.drop_table('sao_player_hero_card') + op.drop_table('sao_player_ex_bonus') + op.drop_table('sao_static_tower') + op.drop_table('sao_static_skill_table') + op.drop_table('sao_static_side_quest') + op.drop_table('sao_static_ex_tower') + op.drop_table('sao_static_ex_bonus') + op.drop_table('sao_static_episode') + op.drop_table('sao_player_tutorial') + op.drop_table('sao_player_resource_card') + op.add_column('sao_static_quest', sa.Column('version', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) + op.drop_column('sao_static_quest', 'TrioRewardVp') + op.drop_column('sao_static_quest', 'PairRewardVp') + op.drop_column('sao_static_quest', 'SingleRewardVp') + op.drop_column('sao_static_quest', 'TrioExpRate') + op.drop_column('sao_static_quest', 'PairExpRate') + op.drop_column('sao_static_quest', 'FailedPlayerExp') + op.drop_column('sao_static_quest', 'SuccessPlayerExp') + op.drop_column('sao_static_quest', 'PlayerTraceTableSubId') + op.drop_column('sao_static_quest', 'RewardTableSubId') + op.drop_column('sao_static_quest', 'LimitResurrection') + op.drop_column('sao_static_quest', 'LimitDefault') + op.drop_column('sao_static_quest', 'ColRate') + op.drop_column('sao_static_quest', 'Tutorial') + op.drop_column('sao_static_quest', 'SortNo') + op.drop_column('sao_static_quest', 'QuestSceneId') + op.add_column('sao_static_quest', sa.Column('enabled', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True)) + op.add_column('sao_static_quest', sa.Column('id', mysql.INTEGER(display_width=11), autoincrement=True, nullable=False)) + op.add_column('sao_static_quest', sa.Column('questSceneId', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) + op.add_column('sao_static_quest', sa.Column('sortNo', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) + op.add_column('sao_static_quest', sa.Column('name', mysql.VARCHAR(length=255), nullable=True)) + op.create_index('sao_static_quest_uk', 'sao_static_quest', ['version', 'questSceneId'], unique=True) + # ### end Alembic commands ### diff --git a/core/data/schema/card.py b/core/data/schema/card.py index 798dd03..6205b4c 100644 --- a/core/data/schema/card.py +++ b/core/data/schema/card.py @@ -25,6 +25,9 @@ aime_card = Table( class CardData(BaseData): + moble_os_codes = set([0x06, 0x07, 0x10, 0x12, 0x13, 0x14, 0x15, 0x17, 0x18]) + card_os_codes = set([0x20, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7]) + async def get_card_by_access_code(self, access_code: str) -> Optional[Row]: sql = aime_card.select(aime_card.c.access_code == access_code) @@ -140,6 +143,11 @@ class CardData(BaseData): if not result: self.logger.error(f"Failed to update IDm to {idm} for {access_code}") + async def set_access_code_by_access_code(self, old_ac: str, new_ac: str) -> None: + result = await self.execute(aime_card.update(aime_card.c.access_code == old_ac).values(access_code=new_ac)) + if not result: + self.logger.error(f"Failed to change card access code from {old_ac} to {new_ac}") + def to_access_code(self, luid: str) -> str: """ Given a felica cards internal 16 hex character luid, convert it to a 0-padded 20 digit access code as a string diff --git a/example_config/sao.yaml b/example_config/sao.yaml index 0209ffe..d405461 100644 --- a/example_config/sao.yaml +++ b/example_config/sao.yaml @@ -2,12 +2,19 @@ server: enable: True loglevel: "info" auto_register: True + photon_app_id: "7df3a2f6-d69d-4073-aafe-810ee61e1cea" + data_version: 1 + game_version: 33 crypt: enable: False key: "" - iv: "" hash: verify_hash: False - hash_base: "" \ No newline at end of file + hash_base: "" + +card: + enable: True + crypt_password: "" + crypt_salt: "" diff --git a/titles/sao/__init__.py b/titles/sao/__init__.py index b6943e6..501af9b 100644 --- a/titles/sao/__init__.py +++ b/titles/sao/__init__.py @@ -1,9 +1,11 @@ from .index import SaoServlet from .const import SaoConstants from .database import SaoData +from .frontend import SaoFrontend from .read import SaoReader index = SaoServlet database = SaoData +frontend = SaoFrontend reader = SaoReader game_codes = [SaoConstants.GAME_CODE] diff --git a/titles/sao/base.py b/titles/sao/base.py index 8d5650d..a589778 100644 --- a/titles/sao/base.py +++ b/titles/sao/base.py @@ -1,448 +1,1173 @@ import logging from csv import * -from random import choice +from random import choice, randint from typing import Dict, List from os import path +import re +from sqlalchemy.engine import Row -from core.data import Data from core import CoreConfig from .config import SaoConfig from .database import SaoData +from .const import GameconnectCmd, RewardType, ExBonusCondition, QuestType from titles.sao.handlers.base import * +import csv class SaoBase: + DATA_LIST = {} def __init__(self, core_cfg: CoreConfig, game_cfg: SaoConfig) -> None: self.core_cfg = core_cfg self.game_cfg = game_cfg self.data = SaoData(core_cfg) - self.version = 0 self.logger = logging.getLogger("sao") - def load_data_csv(self, file: str) -> List[Dict]: - ret = [] - if path.exists(f"titles/sao/data/{file}.csv"): - with open(f"titles/sao/data/{file}.csv", "r", encoding="utf8") as f: - data = csv.DictReader(f, delimiter=',') - for x in data: - ret.append(x) - + def load_data_csv(self, file: str, version: int = 1, base_ver: int = 1) -> List[Dict]: + if base_ver > version: + self.logger.warning(f"load_data_csv: Cannot use base version higher then requested version ({base_ver} > {version})") + return [] + + for x in range(version, base_ver - 1, -1): + ret = self.DATA_LIST.get(x, {}).get(file, []) + if ret: + break + + if not ret and base_ver != 1: + ret = self.DATA_LIST.get(1, {}).get(file, []) + + if ret: return ret - self.logger.warning(f"Failed to find csv file {file}.csv") + found = False + for x in range(version, base_ver - 1, -1): + fname = f"./titles/sao/data/{x}/{file}.csv" + if path.exists(fname): + found_ver = x + found = True + break + + if not found and base_ver != 1: # v1 will always be fallback if it isn't already + fname = f"./titles/sao/data/1/{file}.csv" + if path.exists(fname): + found_ver = 1 + found = True + + if not found: + self.logger.warning(f"load_data_csv: Failed to find v{version} csv file {fname}") + return [] + + ret = [] + with open(fname, "r", encoding="utf8") as f: + data = csv.DictReader(f, delimiter=',') + for x in data: + newdict = {} + for k, v in x.items(): + newkey = k + if k.startswith("// "): + newkey = k.replace("// ", "") + + if v.isdigit(): + newdict[newkey] = int(v) + elif v.lower() == "true": + newdict[newkey] = True + elif v.lower() == "false": + newdict[newkey] = False + elif re.match(r"^\d\d\d\d\/\d\d\/\d\d \d{1,2}:\d\d:\d\d$", v): + newdict[newkey] = datetime.strptime(v, "%Y/%m/%d %H:%M:%S") + elif re.match(r"^\d\d\d\d\/\d\d\/\d\d$", v): + newdict[newkey] = datetime.strptime(v, "%Y/%m/%d") + else: + newdict[newkey] = v + ret.append(newdict) + + # Cache the CSV data in memory + if found_ver not in self.DATA_LIST: + self.DATA_LIST[found_ver] = {} + self.DATA_LIST[found_ver][file] = ret + return ret - async def handle_noop(self, header: SaoRequestHeader, request: bytes) -> bytes: - self.logger.info(f"Using Generic handler") - resp_thing = SaoNoopResponse(header.cmd + 1) - return resp_thing.make() + async def add_reward(self, reward: Dict, user_id: int): + reward_type = int(reward.get("CommonRewardType", "0")) + if reward_type == RewardType.HeroLog: + reward_hero_data = await self.data.static.get_hero_by_id(reward['CommonRewardId']) + now_have_skills = await self.hero_default_skills(reward_hero_data['SkillTableSubId']) + + new_hero_id = await self.data.item.put_hero_log( + user_id, + reward['CommonRewardId'], + 1, + 0, + None, + None, + now_have_skills[0], + now_have_skills[1], + now_have_skills[2], + now_have_skills[3], + now_have_skills[4], + ) + self.logger.info(f"Rewarded user {user_id} with hero {reward['CommonRewardId']} (ID {new_hero_id})") + # TODO: add properties + + elif reward_type == RewardType.Equipment: + new_equip_id = await self.data.item.put_equipment(user_id, reward['CommonRewardId']) + self.logger.info(f"Rewarded user {user_id} with equipment {reward['CommonRewardId']} (ID {new_equip_id})") + + elif reward_type == RewardType.Item: + new_item_id = await self.data.item.put_item(user_id, reward['CommonRewardId']) + self.logger.info(f"Rewarded user {user_id} with item {reward['CommonRewardId']} (ID {new_item_id})") + + elif reward_type == RewardType.Col: + col_num = int(reward['CommonRewardNum']) + self.logger.info(f"Rewarded user {user_id} with {col_num} Col") + await self.data.profile.add_col(user_id, col_num) - async def handle_c122(self, header: SaoRequestHeader, request: bytes) -> bytes: - #common/get_maintenance_info - resp = SaoGetMaintResponse(header.cmd +1) - return resp.make() + elif reward_type == RewardType.VP: + vp_num = int(reward['CommonRewardNum']) + self.logger.info(f"Rewarded user {user_id} with {vp_num} VP") + await self.data.profile.add_vp(user_id, vp_num) - async def handle_c12a(self, header: SaoRequestHeader, request: bytes) -> bytes: - #common/give_free_ticket - req = SaoGiveFreeTicketRequest(header, request) - self.logger.info(f"Give {req.give_num} free tickets (id {req.ticket_id}) to user {req.user_id}") - resp = SaoGiveFreeTicketResponse(header.cmd +1) - return resp.make() + elif reward_type == RewardType.YuiMadal: + medal_num = int(reward['CommonRewardNum']) + self.logger.info(f"Rewarded user {user_id} with {medal_num} Yui Medals") + await self.data.profile.add_yui_medals(user_id, medal_num) + + else: + self.logger.warn(f"User {user_id} Unhandled reward type {reward_type} -> {reward}") - async def handle_c12e(self, header: SaoRequestHeader, request: bytes) -> bytes: - #common/ac_cabinet_boot_notification - resp = SaoCommonAcCabinetBootNotificationResponse(header.cmd +1) - return resp.make() + async def hero_default_skills(self, skill_table_id: int) -> List[int]: + skills = await self.data.static.get_skill_table_by_subid(skill_table_id) + if not skills: + self.logger.error(f"Failed to find skill table {skill_table_id}! Please run the reader") + return [None, None, None, None, None] - async def handle_c100(self, header: SaoRequestHeader, request: bytes) -> bytes: + default_skills = [] + now_have_skills = [None, None, None, None, None] + for skill in skills: + if skill['LevelObtained'] == 1 and skill['AwakeningId'] == 0: + default_skills.append(skill['SkillId']) + + for skill in default_skills: + skill_info = await self.data.static.get_skill_by_id(skill) + skill_slot = skill_info['Level'] - 1 + if now_have_skills[skill_slot] is not None: + now_have_skills[skill] + + return now_have_skills + + async def handle_noop(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + return SaoNoopResponse(header.cmd + 1).make() + + async def handle_c000(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + #ticket/ticket + req = SaoTicketRequest(header, request) + return SaoTicketResponse().make() + + async def handle_c100(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: #common/get_app_versions - resp = SaoCommonGetAppVersionsRequest(header.cmd +1) + resp = SaoGetAppVersionsResponse() + resp.data_list.append(AppVersionData.from_args(self.game_cfg.server.game_version, datetime.fromtimestamp(0))) return resp.make() - async def handle_c102(self, header: SaoRequestHeader, request: bytes) -> bytes: + async def handle_c102(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: #common/master_data_version_check - resp = SaoMasterDataVersionCheckResponse(header.cmd +1) - return resp.make() + req = SaoMasterDataVersionCheckRequest(header, request) + self.logger.info(f"Cab at {src_ip} checked in with master data v{req.current_data_version}") + return SaoMasterDataVersionCheckResponse(self.game_cfg.server.data_version, req.current_data_version).make() - async def handle_c10a(self, header: SaoRequestHeader, request: bytes) -> bytes: - #common/paying_play_start - resp = SaoCommonPayingPlayStartRequest(header.cmd +1) - return resp.make() + async def handle_c104(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + #common/login + req = SaoLoginRequest(header, request) - async def handle_ca02(self, header: SaoRequestHeader, request: bytes) -> bytes: - #quest_multi_play_room/get_quest_scene_multi_play_photon_server - resp = SaoGetQuestSceneMultiPlayPhotonServerResponse(header.cmd +1) - return resp.make() - - async def handle_c11e(self, header: SaoRequestHeader, request: bytes) -> bytes: - #common/get_auth_card_data - req = SaoGetAuthCardDataRequest(header, request) - - #Check authentication user_id = await self.data.card.get_user_id_from_card( req.access_code ) - if not user_id: - user_id = await self.data.user.create_user() #works - card_id = await self.data.card.create_card(user_id, req.access_code) - if req.access_code.startswith("5"): - await self.data.card.set_idm_by_access_code(card_id, req.chip_id[:16]) - elif (req.access_code.startswith("010") or req.access_code.startswith("3")) and int(req.chip_id[:8], 16) != 0x04030201: - await self.data.card.set_chip_id_by_access_code(card_id, int(req.chip_id[:8], 16)) + card = await self.data.card.get_card_by_idm(req.chip_id[:16]) + if card: + user_id = card['user'] + card_id = card['id'] + await self.data.card.set_access_code_by_access_code(card['access_code'], req.access_code) + + else: + user_id = await self.data.user.create_user() #works + card_id = await self.data.card.create_card(user_id, req.access_code) if card_id is None: user_id = -1 self.logger.error("Failed to register card!") - - # Create profile with 3 basic heroes - profile_id = await self.data.profile.create_profile(user_id) - await self.data.item.put_hero_log(user_id, 101000010, 1, 0, 201000000, 0, 1002, 1003, 1014, 30001, 30310) - await self.data.item.put_hero_log(user_id, 102000010, 1, 0, 202000000, 0, 3001, 3002, 3004, 30007, 3011) - await self.data.item.put_hero_log(user_id, 105000010, 1, 0, 209000000, 0, 10005, 10002, 10004, 30006, 10003) - await self.data.item.put_hero_log(user_id, 101000110, 1, 0, 201000000, 101000110, 2002, 2001, 2014, 0, 0) - await self.data.item.put_hero_party(user_id, 0, 101000010, 102000010, 105000010) - await self.data.item.put_equipment_data(user_id, 201000000, 1, 200, 0, 0, 0) - await self.data.item.put_equipment_data(user_id, 202000000, 1, 200, 0, 0, 0) - await self.data.item.put_equipment_data(user_id, 209000000, 1, 200, 0, 0, 0) - await self.data.item.put_equipment_data(user_id, 101000110, 1, 200, 0, 0, 0) - await self.data.item.put_player_quest(user_id, 1001, True, 300, 0, 0, 1) - - # Force the tutorial stage to be completed due to potential crash in-game + self.logger.info(f"Registered card {req.access_code} to user {user_id} from {req.serial_no}") + + if req.access_code.startswith("5"): + await self.data.card.set_idm_by_access_code(req.access_code, req.chip_id[:16]) + elif (req.access_code.startswith("010") or req.access_code.startswith("3")) and int(req.chip_id[:8], 16) != 0x04030201: + await self.data.card.set_chip_id_by_access_code(req.access_code, int(req.chip_id[:8], 16)) + + profile_data = await self.data.profile.get_profile(user_id) - self.logger.info(f"User Authenticated: { req.access_code } | { user_id }") + if not profile_data: + profile_id = await self.data.profile.create_profile(user_id) + if profile_id: + equip1 = await self.data.item.put_equipment(user_id, 101000000) + equip2 = await self.data.item.put_equipment(user_id, 102000000) + equip3 = await self.data.item.put_equipment(user_id, 109000000) + if not equip1 or not equip2 or not equip3: + self.logger.error(f"Failed to create profile for user {user_id} from {req.serial_no} (could not add equipment)") + return SaoNoopResponse(GameconnectCmd.LOGIN_RESPONSE).make() + + hero1 = await self.data.item.put_hero_log(user_id, 101000010, 1, 0, equip1, None, 1002, 1003, 1014, None, None) + hero2 = await self.data.item.put_hero_log(user_id, 102000010, 1, 0, equip2, None, 3001, 3002, 3004, None, None) + hero3 = await self.data.item.put_hero_log(user_id, 105000010, 1, 0, equip3, None, 10005, 10002, 10004, None, None) + if not hero1 or not hero2 or not hero3: + self.logger.error(f"Failed to create profile for user {user_id} from {req.serial_no} (could not add heros)") + return SaoNoopResponse(GameconnectCmd.LOGIN_RESPONSE).make() + + await self.data.item.put_hero_party(user_id, 0, hero1, hero2, hero3) + self.logger.info(f"Create profile {profile_id} for user {user_id} from {req.serial_no}") + else: + self.logger.error(f"Failed to create profile for user {user_id} from {req.serial_no}") + return SaoNoopResponse(GameconnectCmd.LOGIN_RESPONSE).make() + resp = SaoLoginResponse(user_id, True, False) + + else: + is_login_today = False + + if profile_data['last_login_date']: + last_login_time = int(profile_data["last_login_date"].timestamp()) + midnight_today_ts = int( + datetime.now() + .replace(hour=0, minute=0, second=0, microsecond=0) + .timestamp() + ) + + if last_login_time > midnight_today_ts: + is_login_today = True + + if not is_login_today: + await self.data.profile.add_vp(user_id, 100) + + resp = SaoLoginResponse(user_id, profile_data['login_ct'] < 1, is_login_today) + + await self.data.profile.user_login(user_id) + return resp.make() + + async def handle_c106(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # common/logout + req = SaoLogoutRequest(header, request) + self.logger.info(f"User {req.user_id} Logout from {'game' if req.cabinet_type == 0 else 'terminal'} @ {src_ip} with {req.remaining_ticket_num} tickets remaining") + return SaoNoopResponse(GameconnectCmd.LOGOUT_RESPONSE).make() + + async def handle_c108(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # common/logout_ticket_unpurchased + req = SaoLogoutTicketUnpurchasedRequest(header, request) + self.logger.info(f"User {req.user_id} Logout from {'game' if req.cabinet_type == 0 else 'terminal'} @ {src_ip} without buying a ticket") + return SaoNoopResponse(GameconnectCmd.LOGOUT_TICKET_UNPURCHASED_RESPONSE).make() + + async def handle_c10a(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # common/paying_play_start + req = SaoPayingPlayStartRequest(header, request) + self.logger.info(f"User {req.paying_user_id} started paying session @ {req.store_name} ({src_ip}) on cab {req.serial_no}") + resp = SaoPayingPlayStartResponse() + # TODO: session management + return resp.make() + + async def handle_c10c(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # common/paying_play_end + req = SaoPayingPlayEndRequest(header, request) + self.logger.info(f"User {req.paying_user_id} ended paying session {req.paying_session_id} @ {req.store_name} ({src_ip}) on cab {req.serial_no} after {req.played_amount} {req.played_type} type games") + return SaoNoopResponse(GameconnectCmd.PAYING_PLAY_END_RESPONSE).make() + + async def handle_c10e(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # common/purchase_ticket + req = SaoPurchaseTicketRequest(header, request) + self.logger.info(f"User {req.user_id} pruchased {req.purchase_num} tickets (ID {req.ticket_id}) @ {src_ip} with discout type {req.discount_type}") + return SaoNoopResponse(GameconnectCmd.PURCHASE_TICKET_RESPONSE).make() + + async def handle_c110(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # common/consume_ticket + req = SaoConsumeTicketRequest(header, request) + self.logger.info(f"User {req.user_id} consumed {req.consume_num} tickets (ID {req.ticket_id}) @ {src_ip} with discout type {req.discount_type} on {req.act_type}") + return SaoNoopResponse(GameconnectCmd.CONSUME_TICKET_RESPONSE).make() + + async def handle_c112(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # common/add_credit + req = SaoAddCreditRequest(header, request) + self.logger.info(f"User {req.user_id} added {req.add_num} credits to a {'game' if req.cabinet_type == 0 else 'terminal'} @ {src_ip}") + return SaoNoopResponse(GameconnectCmd.ADD_CREDIT_RESPONSE).make() + + async def handle_c114(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # common/consume_credit + req = SaoConsumeCreditRequest(header, request) + self.logger.info(f"User {req.user_id} consumed {req.consume_num} credits on a {'game' if req.cabinet_type == 0 else 'terminal'} @ {req.store_id} ({src_ip}) on {req.act_type}") + return SaoNoopResponse(GameconnectCmd.CONSUME_CREDIT_RESPONSE).make() + + async def handle_c116(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # common/purchase_ticket_guest + req = SaoPurchaseTicketGuestRequest(header, request) + self.logger.info(f"Guest purchased {req.purchase_num} tickets on a {'game' if req.cabinet_type == 0 else 'terminal'} @ {req.store_id} ({src_ip} | SN {req.serial_no})") + return SaoNoopResponse(GameconnectCmd.PURCHASE_TICKET_GUEST_RESPONSE).make() + + async def handle_c118(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # common/consume_ticket_guest + req = SaoConsumeTicketGuestRequest(header, request) + self.logger.info(f"Guest consumed {req.consume_num} tickets @ {req.store_id} ({src_ip} | SN {req.serial_no}) with discout type {req.discount_type} on {req.act_type}") + return SaoNoopResponse(GameconnectCmd.CONSUME_TICKET_GUEST_RESPONSE).make() + + async def handle_c11a(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # common/add_credit_guest + req = SaoAddCreditGuestRequest(header, request) + self.logger.info(f"Guest added {req.add_num} credits to a {'game' if req.cabinet_type == 0 else 'terminal'} @ {req.store_id} ({src_ip} | SN {req.serial_no})") + return SaoNoopResponse(GameconnectCmd.ADD_CREDIT_GUEST_RESPONSE).make() + + async def handle_c11c(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # common/consume_credit_guest + req = SaoConsumeCreditGuestRequest(header, request) + self.logger.info(f"Guest consumed {req.consume_num} credits on a {'game' if req.cab_type == 0 else 'terminal'} @ {req.shop_id} ({src_ip} | SN {req.serial_num}) on {req.act_type}") + return SaoNoopResponse(GameconnectCmd.CONSUME_CREDIT_GUEST_RESPONSE).make() + + async def handle_c11e(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + #common/get_auth_card_data + req = SaoGetAuthCardDataRequest(header, request) + + #Check authentication + card = await self.data.card.get_card_by_access_code( req.access_code ) + + if not card: + card = await self.data.card.get_card_by_idm(req.chip_id[:16]) + if not card: + self.logger.info(f"Unregistered card {req.access_code} authenticated from {req.serial_no}") + return SaoGetAuthCardDataResponse("NEW PLAYER", 0).make() + + await self.data.card.set_access_code_by_access_code(card['access_code'], req.access_code) + + else: + user_id = card['user'] + card_id = card['id'] + + if req.access_code.startswith("5") and not card['idm']: + await self.data.card.set_idm_by_access_code(card_id, req.chip_id[:16]) + elif (req.access_code.startswith("010") or req.access_code.startswith("3")) and not card['chip_id'] and int(req.chip_id[:8], 16) != 0x04030201: + await self.data.card.set_chip_id_by_access_code(card_id, int(req.chip_id[:8], 16)) + + self.logger.info(f"User Authenticated from {req.serial_no}: { req.access_code } | { user_id }") #Grab values from profile profile_data = await self.data.profile.get_profile(user_id) - if user_id and not profile_data: - profile_id = await self.data.profile.create_profile(user_id) - await self.data.item.put_hero_log(user_id, 101000010, 1, 0, 201000000, 0, 1002, 1003, 1014, 30001, 30310) - await self.data.item.put_hero_log(user_id, 102000010, 1, 0, 202000000, 0, 3001, 3002, 3004, 30007, 3011) - await self.data.item.put_hero_log(user_id, 105000010, 1, 0, 209000000, 0, 10005, 10002, 10004, 30006, 10003) - await self.data.item.put_hero_log(user_id, 101000110, 1, 0, 201000000, 101000110, 2002, 2001, 2014, 0, 0) - await self.data.item.put_hero_party(user_id, 0, 101000010, 102000010, 105000010) - await self.data.item.put_equipment_data(user_id, 201000000, 1, 200, 0, 0, 0) - await self.data.item.put_equipment_data(user_id, 202000000, 1, 200, 0, 0, 0) - await self.data.item.put_equipment_data(user_id, 209000000, 1, 200, 0, 0, 0) - await self.data.item.put_equipment_data(user_id, 101000110, 1, 200, 0, 0, 0) - await self.data.item.put_player_quest(user_id, 1001, True, 300, 0, 0, 1) + if not profile_data: + self.logger.info(f"Unregistered user {user_id} with card {req.access_code} authenticated from {req.serial_no}") + return SaoGetAuthCardDataResponse("NEW PLAYER", user_id).make() - # Force the tutorial stage to be completed due to potential crash in-game + return SaoGetAuthCardDataResponse(profile_data['nick_name'], user_id).make() + async def handle_c120(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # common/get_access_code_by_keitai + req = SaoGetAccessCodeByKeitaiRequest(header, request) + cid = req.chip_id + idm = cid[:16] + pmm = cid[16:] + card = await self.data.card.get_card_by_idm(idm) + + # If we don't have that card saved locally, check aimedb + if not card: + # Validate that we're talking to a phone + if not int(pmm[2:4], 16) in self.data.card.moble_os_codes: + self.logger.warn(f"{req.serial_no} looked up non-moble chip ID {cid}!") + return SaoGetAccessCodeByKeitaiResponse("").make() + + # TODO: Actual felica moble registration + return SaoGetAccessCodeByKeitaiResponse("").make() + #ac = await self.data.card.register_felica_moble_ac(idm, pmm) + # if we didn't get an access code, fail hard + if not ac: + self.logger.warn(f"Failed to register access code for chip ID {cid} requested by {req.serial_no}") + return SaoGetAccessCodeByKeitaiResponse("").make() + + self.logger.info(f"Successfully registered moble felica access code {ac} for chip ID {cid} requested by {req.serial_no}") + + uid = await self.data.user.create_user() + if not uid: + self.logger.error(f"Failed to create user for chip ID {cid} (access code {ac}) @ LoadAccessCode request from {req.serial_no}") + return SaoGetAccessCodeByKeitaiResponse("").make() + + cardid = await self.data.card.create_card(uid, ac) + if not cardid: + self.logger.error(f"Failed to create card for user {uid} with chip ID {cid} (access code {ac}) @ LoadAccessCode request from {req.serial_no}") + await self.data.user.delete_user(uid) + return SaoGetAccessCodeByKeitaiResponse("").make() + + self.logger.info(f"Moble Felica access code lookup for {cid} -> {ac} (user {uid}) requested by {req.serial_no}") + + else: + ac = card['access_code'] + uid = card['user'] + self.logger.info(f"Moble Felica access code for {cid} -> {ac} (user {uid}) requested by {req.serial_no}") + + return SaoGetAccessCodeByKeitaiResponse(ac).make() - profile_data = await self.data.profile.get_profile(user_id) - - resp = SaoGetAuthCardDataResponse(header.cmd +1, profile_data) + async def handle_c122(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + #common/get_maintenance_info + resp = SaoGetMaintenanceInfoResponse() return resp.make() - async def handle_c40c(self, header: SaoRequestHeader, request: bytes) -> bytes: - #home/check_ac_login_bonus - resp = SaoHomeCheckAcLoginBonusResponse(header.cmd +1) + async def handle_c124(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + #common/get_resource_path_info + resp = SaoGetResourcePathInfoResponse(f"https://{self.core_cfg.server.hostname}/saoresource/") return resp.make() - async def handle_c104(self, header: SaoRequestHeader, request: bytes) -> bytes: - #common/login - req = SaoCommonLoginRequest(header, request) + async def handle_c126(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # common/validation_error_notification + req = SaoValidationErrorNotificationRequest(header, request) + self.logger.warn(f"User {req.user_id} on {'game' if req.cabinet_type == 0 else 'terminal'} {req.serial_no} @ {req.store_name} ({src_ip} | Place ID {req.place_id}) " \ + + f"Validation error: {req.send_protocol_name} || {req.send_data_to_fraud_value} || {req.send_data_to_modification_value}") + return SaoNoopResponse(GameconnectCmd.VALIDATION_ERROR_NOTIFICATION_RESPONSE).make() - user_id = await self.data.card.get_user_id_from_card( req.access_code ) - profile_data = await self.data.profile.get_profile(user_id) + async def handle_c128(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # common/power_cutting_return_notification + req = SaoPowerCuttingReturnNotification(header, request) + self.logger.warn(f"User {req.user_id} on {'game' if req.cabinet_type == 0 else 'terminal'} {req.serial_no} @ {req.store_name} ({src_ip} | Place ID {req.place_id}) " \ + + f"Power outage return: Act Type {req.last_act_type} || {req.remaining_ticket_num} Remaining Tickets || {req.remaining_credit_num} Remaining Credits") + return SaoNoopResponse(GameconnectCmd.POWER_CUTTING_RETURN_NOTIFICATION_RESPONSE).make() - resp = SaoCommonLoginResponse(header.cmd +1, profile_data) + async def handle_c12a(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + #common/give_free_ticket + req = SaoGiveFreeTicketRequest(header, request) + self.logger.info(f"Give {req.give_num} free tickets (id {req.ticket_id}) to user {req.user_id}") + resp = SaoNoopResponse(GameconnectCmd.GIVE_FREE_TICKET_RESPONSE) return resp.make() - async def handle_c404(self, header: SaoRequestHeader, request: bytes) -> bytes: - #home/check_comeback_event - resp = SaoCheckComebackEventRequest(header.cmd +1) + async def handle_c12c(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # common/matching_error_notification + req = SaoMatchingErrorNotificationRequest(header, request) + self.logger.warn(f"{'game' if req.cabinet_type == 0 else 'terminal'} {req.serial_no} @ {req.store_name} ({src_ip} | Place ID {req.place_id}) " \ + + f"Matching error: {req.matching_error_data_list[0]}") + return SaoNoopResponse(GameconnectCmd.MATCHING_ERROR_NOTIFICATION_RESPONSE).make() + + async def handle_c12e(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + #common/ac_cabinet_boot_notification + req = SaoCommonAcCabinetBootNotificationRequest(header, request) + + if req.current_version_app_id < self.game_cfg.server.game_version: + self.logger.info(f"!!OUTDATED!! {'Game' if req.cabinet_type == 0 else 'Terminal'} {req.serial_no} Booted v{req.current_version_app_id} (Master data v{req.current_master_data_version}): {req.store_name} ({src_ip} | Place/Shop ID {req.place_id}/{req.store_id})") + + if req.current_version_app_id > self.game_cfg.server.game_version: + self.logger.info(f"!!TOO NEW!! {'Game' if req.cabinet_type == 0 else 'Terminal'} {req.serial_no} Booted v{req.current_version_app_id} (Master data v{req.current_master_data_version}): {req.store_name} ({src_ip} | Place/Shop ID {req.place_id}/{req.store_id})") + + self.logger.info(f"{'Game' if req.cabinet_type == 0 else 'Terminal'} {req.serial_no} Booted v{req.current_version_app_id} (Master data v{req.current_master_data_version}): {req.store_name} ({src_ip} | Place/Shop ID {req.place_id}/{req.store_id})") + resp = SaoNoopResponse(GameconnectCmd.AC_CABINET_BOOT_NOTIFICATION_RESPONSE) return resp.make() - async def handle_c000(self, header: SaoRequestHeader, request: bytes) -> bytes: - #ticket/ticket - resp = SaoTicketResponse(header.cmd +1) + async def handle_c200(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # tutorial/first_tutorial_end + req = SaoGenericUserTicketRequest(header, request) + self.logger.info(f"User {req.user_id} (ticket {req.ticket_id}) finished first tutorial") + return SaoNoopResponse(GameconnectCmd.FIRST_TUTORIAL_END_RESPONSE).make() + + async def handle_c202(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # tutorial/various_tutorial_end + req = SaoVariousTutorialEndRequest(header, request) + await self.data.profile.add_tutorial_byte(int(req.user_id), req.tutorial_type) + return SaoNoopResponse(GameconnectCmd.VARIOUS_TUTORIAL_END_RESPONSE).make() + + async def handle_c204(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # tutorial/get_various_tutorial_data_list + req = SaoGenericUserRequest(header, request) + tuts = await self.data.profile.get_tutorial_bytes(int(req.user_id)) + resp = SaoGetVariousTutorialDataListResponse() + if tuts: + for t in tuts: + resp.end_tutorial_type_list.append(t['tutorial_byte']) + return resp.make() - async def handle_c500(self, header: SaoRequestHeader, request: bytes) -> bytes: - #user_info/get_user_basic_data + async def handle_c300(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # card/discharge_profile_card + req = SaoDischargeProfileCardRequest(header, request) + # Real cards seem to start with 10-17 as the first 2 digits, so we'll anchor ours with 2 to ensure no overlap + sn = f"2{str(randint(1, 999999999999999999)).zfill(18)}" + while await self.data.profile.get_hero_card(sn): + sn = f"2{str(randint(1, 999999999999999999)).zfill(18)}" + resp = SaoDischargeProfileCardResponse(sn) + + db_hero = await self.data.item.get_hero_log(req.user_id, req.hero_log_user_hero_log_id) + if not db_hero: + hero_statc = await self.data.static.get_hero_by_id(db_hero['hero_log_id']) + if not hero_statc: + self.logger.error(f"Failed to find hero log {db_hero['hero_log_id']}! Please run the reader") + resp.header.error_type = ProtocolErrorNum.RESOURCE_CARD_ERR1 + return resp.make() + + now_have_skills = await self.hero_default_skills(hero_statc['SkillTableSubId']) + + db_hero_id = await self.data.item.put_hero_log( + req.user_id, + db_hero['hero_log_id'], + 1, + 0, + None, + None, + now_have_skills[0], + now_have_skills[1], + now_have_skills[2], + now_have_skills[3], + now_have_skills[4] + ) + if not db_hero_id: + self.logger.error(f"Failed to give user {req.user_id} hero {db_hero['hero_log_id']}!") + resp.header.error_type = ProtocolErrorNum.RESOURCE_CARD_ERR6 + return resp.make() + + else: + db_hero_id = db_hero['id'] + + await self.data.profile.put_hero_card(req.user_id, sn, db_hero_id, req.holographic_flag) + + self.logger.info(f"User {req.user_id} printed {'holo ' if req.holographic_flag == 1 else ''}profile card {req.hero_log_user_hero_log_id} {req.execute_print_type}, code {sn}") + return resp.make() + + async def handle_c302(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # card/discharge_resource_card + req = SaoDischargeResourceCardRequest(header, request) + + for x in req.common_reward_user_data: + sn = f"2{str(randint(1, 999999999999999999)).zfill(18)}" + self.logger.info(f"User {req.user_id} printed {'holo ' if req.holographic_flag == 1 else ''}resource card {x.user_common_reward_id} {req.execute_print_type}, code {sn}") + await self.data.profile.put_resource_card(req.user_id, sn, x.common_reward_type, x.user_common_reward_id, req.holographic_flag) + + return SaoDischargeProfileCardResponse(sn).make() + + async def handle_c304(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # card/discharge_resource_card_complete + req = SaoDischargeResourceCardCompleteRequest(header, request) + self.logger.info(f"User {req.user_id} finished printing resource card {req.resource_card_code}") + return SaoNoopResponse(GameconnectCmd.DISCHARGE_RESOURCE_CARD_COMPLETE_RESPONSE).make() + + async def handle_c306(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + #card/scan_qr_quest_profile_card + req = SaoScanQrQuestProfileCardRequest(header, request) + resp = SaoScanQrQuestProfileCardResponse() + + card = await self.data.profile.get_hero_card(req.profile_card_code) + if not card: + self.logger.warn(f"User {req.user_id} scanned unregistered QR code {req.profile_card_code}") + return resp.make() + + hero = await self.data.item.get_hero_log_by_id(card['user_hero_id']) + if not hero: # Shouldn't happen + self.logger.warn(f"User {req.user_id} scanned QR code {req.profile_card_code} but does not have hero entry {card['user_hero_id']}") + return resp.make() + + hero_static_data = await self.data.static.get_hero_by_id(hero['hero_log_id']) + if not hero_static_data: # Shouldn't happen + self.logger.warn(f"No entry for hero {hero['hero_log_id']}, please run read.py") + return resp.make() + + profile = await self.data.profile.get_profile(card['user']) + if not profile: # Shouldn't happen + self.logger.warn(f"No profile for user {card['user']}, something broke") + return resp.make() + + self.logger.info(f"User {req.user_id} scanned QR code {req.profile_card_code}") + card_resp = ReadProfileCard.from_args(req.profile_card_code, profile['nick_name']) + card_resp.rank_num = profile['rank_num'] + card_resp.setting_title_id = profile['setting_title_id'] + card_resp.skill_id = hero['skill_slot1_skill_id'] + card_resp.hero_log_hero_log_id = hero['hero_log_id'] + card_resp.hero_log_log_level = hero['log_level'] + #TODO: Awakening + #card_resp.hero_log_awakening_stage = hero['log_level'] + + resp.profile_card_data.append(card_resp) + return resp.make() + + async def handle_c308(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # card/scan_qr_shop_resource_card + req = SaoScanQrShopResourceCardRequest(header, request) + self.logger.info(f"User {req.user_id} scanned shop resource card {req.resource_card_code}") + resp = SaoNoopResponse(GameconnectCmd.SCAN_QR_SHOP_RESOURCE_CARD_RESPONSE) + # On official, resource cards have limited uses, but we don't track that currently (tho we should) + + card = await self.data.profile.get_resource_card(req.resource_card_code) # TODO: use count + if not card: + self.logger.warn(f"No resource card with serial {req.resource_card_code} exists!") + resp.header.err_status = 4832 # Theres a few error codes but none seem to do anything? + # Also not sure if it should be this or result + + return resp.make() + + async def handle_c30a(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # card/scan_qr_quest_resource_card + req = SaoScanQrQuestResourceCardRequest(header, request) + self.logger.info(f"User {req.user_id} scanned quest resource card {req.resource_card_code}") + + card = await self.data.profile.get_resource_card(req.resource_card_code) + if not card: + resp = SaoScanQrQuestResourceCardResponse(card['common_reward_type'], card['common_reward_id'], card['holographic_flag']) + + else: + self.logger.warn(f"No resource card with serial {req.resource_card_code} exists!") + resp = SaoScanQrQuestResourceCardResponse() + resp.header.err_status = 4832 # Theres a few error codes but none seem to do anything? + # Also not sure if it should be this or result + + return resp.make() + + async def handle_c400(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + #home/check_yui_medal_get_condition + req = SaoCheckYuiMedalGetConditionRequest(header, request) + profile = await self.data.profile.get_profile(req.user_id) + if profile['last_yui_medal_date']: + last_check_ts = int(profile['last_yui_medal_date'].timestamp()) + day_ts = int(datetime.now().replace(hour=0, minute=0, second=0, microsecond=0).timestamp()) + diff_ts = day_ts - last_check_ts + if diff_ts > 0: + num_days = diff_ts / 86400 + else: + num_days = 0 + else: + num_days = 1 + + if num_days > 1: + await self.data.profile.add_yui_medals(req.user_id) + + await self.data.profile.update_yui_medal_date(req.user_id) + return SaoCheckYuiMedalGetConditionResponse(num_days, 1 if num_days > 1 else 0).make() + + async def handle_c402(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + #home/get_yui_medal_bonus_user_data + req = SaoGetYuiMedalBonusUserDataRequest(header, request) + resp = SaoGetYuiMedalBonusUserDataResponse() # TODO: Track yui login bonus + return resp.make() + + async def handle_c404(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # home/check_comeback_event + req = SaoGenericUserTicketRequest(header, request) + resp = SaoCheckComebackEventResponse() + #resp.get_comeback_event_id_list += [1,2,3,4] # TODO: track comeback date + return resp.make() + + async def handle_c406(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # home/change_my_store + req = SaoChangeMyStoreRequest(header, request) + self.logger.info(f"User {req.user_id} changed My Store to {req.store_id}") + shop_id = int(req.store_id[3:], 16) + await self.data.profile.set_my_shop(req.user_id, shop_id) + return SaoNoopResponse(GameconnectCmd.CHANGE_MY_STORE_RESPONSE).make() + + async def handle_c408(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # home/check_title_get_decision + req = SaoCheckTitleGetDecisionRequest(header, request) + resp = SaoCheckTitleGetDecisionResponse() # TODO: titles + return resp.make() + + async def handle_c40a(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # home/check_profile_card_used_reward + req = SaoCheckProfileCardUsedRewardRequest(header, request) + resp = SaoCheckProfileCardUsedRewardResponse() # TODO: check_profile_card_used_reward + return resp.make() + + async def handle_c40c(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # home/check_ac_login_bonus + req = SaoGenericUserTicketRequest(header, request) + resp = SaoCheckAcLoginBonusResponse() + #resp.get_ac_login_bonus_id_list.append(1) # TODO: track login bonus date + #resp.get_ac_login_bonus_id_list.append(2) + return resp.make() + + async def handle_c500(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # user_info/get_user_basic_data req = SaoGetUserBasicDataRequest(header, request) profile_data = await self.data.profile.get_profile(req.user_id) + player_rank_data = self.load_data_csv("PlayerRank") - resp = SaoGetUserBasicDataResponse(header.cmd +1, profile_data) - return resp.make() + resp = SaoGetUserBasicDataResponse(profile_data) + for e in player_rank_data: + if resp.user_basic_data[0].rank_num == int(e['PlayerRankId']): + resp.user_basic_data[0].rank_exp = resp.user_basic_data[0].rank_exp - int(e["TotalExp"]) + break - async def handle_c600(self, header: SaoRequestHeader, request: bytes) -> bytes: - #have_object/get_hero_log_user_data_list - req = SaoGetHeroLogUserDataListRequest(header, request) + if profile_data['my_shop']: + ac = await self.data.arcade.get_arcade(profile_data['my_shop']) + if ac: + resp.user_basic_data[0].my_store_name = ac['name'] + + return resp.make() + + async def handle_c502(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # user_info/get_vp_gasha_ticket_data_list + req = SaoGetVpGashaTicketDataListRequest(header, request) + # TODO: gasha tickets + resp = SaoGetVpGashaTicketDataListResponse() + return resp.make() + + async def handle_c504(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # user_info/get_present_box_num + req = SaoGenericUserRequest(header, request) + # TODO: presents + return SaoGetPresentBoxNumResponse().make() + + async def handle_c600(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # have_object/get_hero_log_user_data_list + req = SaoGenericUserRequest(header, request) + hero_level_data = self.load_data_csv("HeroLogLevel") hero_data = await self.data.item.get_hero_logs(req.user_id) - resp = SaoGetHeroLogUserDataListResponse(header.cmd +1, hero_data) + resp = SaoGetHeroLogUserDataListResponse() + for hero in hero_data: + append = HeroLogUserData.from_args(hero) + hero_static = await self.data.static.get_hero_by_id(hero['hero_log_id']) + if not hero_static: + self.logger.warn(f"No hero for id {hero['hero_log_id']}, please run reader") + resp.hero_log_user_data_list.append(append) + continue + + append.property1_property_id = hero_static['Property1PropertyId'] if hero_static['Property1PropertyId'] else 0 + append.property1_value1 = hero_static['Property1Value1'] if hero_static['Property1Value1'] else 0 + append.property1_value2 = hero_static['Property1Value2'] if hero_static['Property1Value2'] else 0 + append.property2_property_id = hero_static['Property2PropertyId'] if hero_static['Property2PropertyId'] else 0 + append.property2_value1 = hero_static['Property2Value1'] if hero_static['Property2Value1'] else 0 + append.property2_value2 = hero_static['Property2Value2'] if hero_static['Property2Value2'] else 0 + append.property3_property_id = hero_static['Property3PropertyId'] if hero_static['Property3PropertyId'] else 0 + append.property3_value1 = hero_static['Property3Value1'] if hero_static['Property3Value1'] else 0 + append.property3_value2 = hero_static['Property3Value2'] if hero_static['Property3Value2'] else 0 + append.property4_property_id = hero_static['Property4PropertyId'] if hero_static['Property4PropertyId'] else 0 + append.property4_value1 = hero_static['Property4Value1'] if hero_static['Property4Value1'] else 0 + append.property4_value2 = hero_static['Property4Value2'] if hero_static['Property4Value2'] else 0 + + for e in hero_level_data: + if hero['log_level'] == int(e['HeroLogLevelId']): + append.log_exp = hero['log_exp'] - int(e["TotalExp"]) + break + resp.hero_log_user_data_list.append(append) + return resp.make() - - async def handle_c602(self, header: SaoRequestHeader, request: bytes) -> bytes: - #have_object/get_equipment_user_data_list - req = SaoGetEquipmentUserDataListRequest(header, request) + + async def handle_c602(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # have_object/get_equipment_user_data_list + req = SaoGenericUserRequest(header, request) + resp = SaoGetEquipmentUserDataListResponse() + equip_level_data = self.load_data_csv("EquipmentLevel") equipment_data = await self.data.item.get_user_equipments(req.user_id) + + if equipment_data: + for equipment in equipment_data: + e = EquipmentUserData.from_args(equipment) + weapon_static = await self.data.static.get_equipment_by_id(equipment['equipment_id']) + if not weapon_static: + self.logger.warn(f"No equipment for id {equipment['equipment_id']}, please run reader") + resp.equipment_user_data_list.append(e) + continue + + if not e.property1_property_id: + e.property1_property_id = weapon_static['Property1PropertyId'] if weapon_static['Property1PropertyId'] else 0 + e.property1_value1 = weapon_static['Property1Value1'] if weapon_static['Property1Value1'] else 0 + e.property1_value2 = weapon_static['Property1Value2'] if weapon_static['Property1Value2'] else 0 + + if not e.property2_property_id: + e.property2_property_id = weapon_static['Property2PropertyId'] if weapon_static['Property2PropertyId'] else 0 + e.property2_value1 = weapon_static['Property2Value1'] if weapon_static['Property2Value1'] else 0 + e.property2_value2 = weapon_static['Property2Value2'] if weapon_static['Property2Value2'] else 0 + + if e.property3_property_id: + e.property3_property_id = weapon_static['Property3PropertyId'] if weapon_static['Property3PropertyId'] else 0 + e.property3_value1 = weapon_static['Property3Value1'] if weapon_static['Property3Value1'] else 0 + e.property3_value2 = weapon_static['Property3Value2'] if weapon_static['Property3Value2'] else 0 + + if e.property4_property_id: + e.property4_property_id = weapon_static['Property4PropertyId'] if weapon_static['Property4PropertyId'] else 0 + e.property4_value1 = weapon_static['Property4Value1'] if weapon_static['Property4Value1'] else 0 + e.property4_value2 = weapon_static['Property4Value2'] if weapon_static['Property4Value2'] else 0 + + for f in equip_level_data: + if equipment['enhancement_value'] == int(f['EquipmentLevelId']): + e.enhancement_exp = equipment['enhancement_exp'] - int(f["TotalExp"]) + break + resp.equipment_user_data_list.append(e) - resp = SaoGetEquipmentUserDataListResponse(header.cmd +1, equipment_data) return resp.make() - - async def handle_c604(self, header: SaoRequestHeader, request: bytes) -> bytes: - #have_object/get_item_user_data_list - req = SaoGetItemUserDataListRequest(header, request) + + async def handle_c604(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # have_object/get_item_user_data_list + req = SaoGenericUserRequest(header, request) item_data = await self.data.item.get_user_items(req.user_id) - resp = SaoGetItemUserDataListResponse(header.cmd +1, item_data) + resp = SaoGetItemUserDataListResponse(item_data) return resp.make() + + async def handle_c606(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # have_object/get_support_log_user_data_list + req = SaoGenericUserRequest(header, request) + resp = SaoGetSupportLogUserDataListResponse() + supports = self.load_data_csv("SupportLog") + # TODO: Save supports - async def handle_c606(self, header: SaoRequestHeader, request: bytes) -> bytes: - #have_object/get_support_log_user_data_list - supportIdsData = await self.data.static.get_support_log_ids(0, True) - - resp = SaoGetSupportLogUserDataListResponse(header.cmd +1, supportIdsData) + for x in range(len(supports)): + tmp = SupportLogUserData.from_args(f"{req.user_id}{x}", supports[x]['SupportLogId']) + resp.support_log_user_data_list.append(tmp) + return resp.make() + + async def handle_c608(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + #have_object/get_episode_append_data_list + req = SaoGenericUserRequest(header, request) + # TODO: Appends + resp = SaoGetEpisodeAppendDataListResponse() return resp.make() - async def handle_c800(self, header: SaoRequestHeader, request: bytes) -> bytes: - #custom/get_title_user_data_list + async def handle_c60a(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # have_object/get_event_item_data_list + req = SaoGenericUserRequest(header, request) + res = SaoGetEventItemDataListResponse() + # TODO: Event items maybe + return res.make() + + async def handle_c60c(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # have_object/get_gasha_medal_user_data_list + req = SaoGenericUserRequest(header, request) + res = SaoGetGashaMedalUserDataListResponse() + # TODO: Gasha Medal data + return res.make() + + async def handle_c700(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # shop/get_shop_resource_sales_data_list + # TODO: Get user shop data + req = SaoGenericUserRequest(header, request) + resp = SaoGetShopResourceSalesDataListResponse() + return resp.make() + + async def handle_c702(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # shop/purchase_shop_resource + req = SaoPurchaseShopResourceRequest(header, request) + self.logger.infof(f"User {req.user_id} (ticket {req.ticket_id}) purchased shop resourse {req.user_shop_resource_id}") + # TODO: Shop purchases + return SaoNoopResponse(GameconnectCmd.PURCHASE_SHOP_RESOURCE_RESPONSE).make() + + async def handle_c704(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # shop/discard_shop_resource + req = SaoPurchaseShopResourceRequest(header, request) + self.logger.infof(f"User {req.user_id} (ticket {req.ticket_id}) discarded shop resourse {req.user_shop_resource_id}") + return SaoNoopResponse(GameconnectCmd.DISCARD_SHOP_RESOURCE_RESPONSE).make() + + async def handle_c800(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # custom/get_title_user_data_list + req = SaoGenericUserRequest(header, request) titleIdsData = await self.data.static.get_title_ids(0, True) + # TODO: Save titles - resp = SaoGetTitleUserDataListResponse(header.cmd +1, titleIdsData) + resp = SaoGetTitleUserDataListResponse(req.user_id, titleIdsData) return resp.make() + + async def handle_c802(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # custom/change_title + req = SaoChangeTitleRequest(header, request) + self.logger.info(f"User {req.user_id} (ticket {req.ticket_id}) changed their title to {req.user_title_id}") + await self.data.profile.set_title(req.user_id, req.user_title_id) - async def handle_c608(self, header: SaoRequestHeader, request: bytes) -> bytes: - #have_object/get_episode_append_data_list - req = SaoGetEpisodeAppendDataListRequest(header, request) + return SaoNoopResponse(GameconnectCmd.CHANGE_TITLE_RESPONSE).make() - profile_data = await self.data.profile.get_profile(req.user_id) + async def handle_c804(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # custom/get_party_data_list + req = SaoGenericUserRequest(header, request) + resp = SaoGetPartyDataListResponse() + + hero_parties = await self.data.item.get_hero_party(req.user_id) + for party in hero_parties: + hero1_data = await self.data.item.get_user_hero_by_id(party['user_hero_log_id_1']) + hero2_data = await self.data.item.get_user_hero_by_id(party['user_hero_log_id_2']) + hero3_data = await self.data.item.get_user_hero_by_id(party['user_hero_log_id_3']) + + resp.party_data_list.append(PartyData.from_args(party['id'], party['user_party_team_id'], hero1_data._asdict(), hero2_data._asdict(), hero3_data._asdict())) - resp = SaoGetEpisodeAppendDataListResponse(header.cmd +1, profile_data) return resp.make() - async def handle_c804(self, header: SaoRequestHeader, request: bytes) -> bytes: - #custom/get_party_data_list - req = SaoGetPartyDataListRequest(header, request) - - hero_party = await self.data.item.get_hero_party(req.user_id, 0) - hero1_data = await self.data.item.get_hero_log(req.user_id, hero_party[3]) - hero2_data = await self.data.item.get_hero_log(req.user_id, hero_party[4]) - hero3_data = await self.data.item.get_hero_log(req.user_id, hero_party[5]) - - resp = SaoGetPartyDataListResponse(header.cmd +1, hero1_data, hero2_data, hero3_data) + async def handle_c808(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # custom/get_support_log_party_data_list + req = SaoGenericUserRequest(header, request) + resp = SaoGetSupportLogPartyDataListResponse() + # TODO: Support logs return resp.make() - async def handle_c902(self, header: SaoRequestHeader, request: bytes) -> bytes: # for whatever reason, having all entries empty or filled changes nothing - #quest/get_quest_scene_prev_scan_profile_card - resp = SaoGetQuestScenePrevScanProfileCardResponse(header.cmd +1) - return resp.make() + async def handle_c812(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # custom/disposal_resource + req = SaoDisposalResourceRequest(header, request) + get_col = 0 + for disposal in req.disposal_common_reward_user_data_list: + if disposal.common_reward_type == RewardType.HeroLog: + removed_hero = await self.data.item.remove_hero_log(disposal.user_common_reward_id) + if removed_hero: + static_hero = await self.data.static.get_hero_by_id(removed_hero) + get_col += static_hero['SalePrice'] + + elif disposal.common_reward_type == RewardType.Equipment: + removed_equip = await self.data.item.remove_equipment(disposal.user_common_reward_id) + if removed_equip: + static_equip = await self.data.static.get_equipment_by_id(removed_equip) + get_col += static_equip['SalePrice'] - async def handle_c124(self, header: SaoRequestHeader, request: bytes) -> bytes: - #common/get_resource_path_info - resp = SaoGetResourcePathInfoResponse(header.cmd +1) - return resp.make() + elif disposal.common_reward_type == RewardType.Item: + removed_equip = await self.data.item.remove_item(disposal.user_common_reward_id) + if removed_equip: + static_equip = await self.data.static.get_equipment_by_id(removed_equip) + get_col += static_equip['SalePrice'] - async def handle_c900(self, header: SaoRequestHeader, request: bytes) -> bytes: - #quest/get_quest_scene_user_data_list // QuestScene.csv - req = SaoGetQuestSceneUserDataListRequest(header, request) + elif disposal.common_reward_type == RewardType.SupportLog: + continue - quest_data = await self.data.item.get_quest_logs(req.user_id) + else: + self.logger.warn(f"Unhandled disposal type {disposal.common_reward_type}") - resp = SaoGetQuestSceneUserDataListResponse(header.cmd +1, quest_data) - return resp.make() + await self.data.profile.add_col(req.user_id, get_col) + return SaoDisposalResourceResponse(get_col).make() - async def handle_c400(self, header: SaoRequestHeader, request: bytes) -> bytes: - #home/check_yui_medal_get_condition - resp = SaoCheckYuiMedalGetConditionResponse(header.cmd +1) - return resp.make() - - async def handle_c402(self, header: SaoRequestHeader, request: bytes) -> bytes: - #home/get_yui_medal_bonus_user_data - resp = SaoGetYuiMedalBonusUserDataResponse(header.cmd +1) - return resp.make() - - async def handle_c40a(self, header: SaoRequestHeader, request: bytes) -> bytes: - #home/check_profile_card_used_reward - resp = SaoCheckProfileCardUsedRewardResponse(header.cmd +1) - return resp.make() - - async def handle_c814(self, header: SaoRequestHeader, request: bytes) -> bytes: + async def handle_c814(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: #custom/synthesize_enhancement_hero_log req = SaoSynthesizeEnhancementHeroLogRequest(header, request) - - synthesize_hero_log_data = await self.data.item.get_hero_log(req.user_id, req.origin_user_hero_log_id) + resp = SaoSynthesizeEnhancementHeroLogResponse() + hero_level_data = self.load_data_csv("HeroLogLevel") + equip_level_data = self.load_data_csv("EquipmentLevel") + hero_exp = 0 + col_cost = 0 for x in req.material_common_reward_user_data_list: - hero_exp = 0 - itemList = await self.data.static.get_item_id(x.user_common_reward_id) - heroList = await self.data.static.get_hero_id(x.user_common_reward_id) - equipmentList = await self.data.static.get_equipment_id(x.user_common_reward_id) + if x.common_reward_type == RewardType.Item: + user_item = await self.data.item.get_user_item_by_id(x.user_common_reward_id) + static_item = await self.data.static.get_item_id(user_item['item_id']) + + if int(static_item['ItemTypeId']) == 7: + hero_exp += int(static_item['Value']) + self.logger.info(f"Remove item {x.user_common_reward_id} and add {int(static_item['Value'])} XP (running {hero_exp})") + await self.data.item.remove_item(x.user_common_reward_id) - if itemList: - hero_exp = 2000 + int(synthesize_hero_log_data["log_exp"]) - await self.data.item.remove_item(req.user_id, x.user_common_reward_id) - - if equipmentList: - equipment_data = await self.data.item.get_user_equipment(req.user_id, x.user_common_reward_id) + elif x.common_reward_type == RewardType.Equipment: + equipment_data = await self.data.item.get_user_equipment_by_id(x.user_common_reward_id) if equipment_data is None: self.logger.error(f"Failed to find equipment {x.user_common_reward_id} for user {req.user_id}!") continue - hero_exp = int(equipment_data["enhancement_exp"]) + int(synthesize_hero_log_data["log_exp"]) - await self.data.item.remove_equipment(req.user_id, x.user_common_reward_id) + req_exp = 0 + for e in range(len(equip_level_data)): + if equipment_data['enhancement_value'] == int(equip_level_data[e]['EquipmentLevelId']): + req_exp = equip_level_data[e + 1]['RequireExp'] + break + + static_equip_data = await self.data.static.get_equipment_by_id(equipment_data['equipment_id']) + + hero_exp += int(static_equip_data['CompositionExp']) + req_exp + self.logger.info(f"Remove equipment {x.user_common_reward_id} and add {int(static_equip_data['CompositionExp']) + req_exp} XP (running {hero_exp})") + await self.data.item.remove_equipment(x.user_common_reward_id) - if heroList: - hero_data = await self.data.item.get_hero_log(req.user_id, x.user_common_reward_id) + elif x.common_reward_type == RewardType.HeroLog: + hero_data = await self.data.item.get_hero_log_by_id(x.user_common_reward_id) if hero_data is None: self.logger.error(f"Failed to find hero {x.user_common_reward_id} for user {req.user_id}!") continue + + req_exp = 0 + for e in range(len(hero_level_data)): + if hero_data['log_level'] == int(hero_level_data[e]['HeroLogLevelId']): + req_exp = hero_level_data[e + 1]['RequireExp'] + break - hero_exp = int(hero_data["log_exp"]) + int(synthesize_hero_log_data["log_exp"]) - await self.data.item.remove_hero_log(req.user_id, x.user_common_reward_id) + static_hero_data = await self.data.static.get_hero_by_id(hero_data['hero_log_id']) + + hero_exp += int(static_hero_data['CompositionExp']) + req_exp + self.logger.info(f"Remove hero {x.user_common_reward_id} and add {int(static_hero_data['CompositionExp']) + req_exp} XP (running {hero_exp})") + await self.data.item.remove_hero_log(x.user_common_reward_id) - if hero_exp == 0: - self.logger.warn(f"Hero {x.user_common_reward_id} (type {x.common_reward_type}) not found!") + else: + self.logger.warn(f"Unhandled ype {x.common_reward_type}! (running {hero_exp})") + + hero_exp = int(hero_exp * 1.5) + await self.data.item.add_hero_xp(req.origin_user_hero_log_id, hero_exp) + log_exp = await self.data.item.get_hero_xp(req.origin_user_hero_log_id) + pre_synth_xp = log_exp - hero_exp + pre_synth_level = 1 + + for e in range(len(hero_level_data)): + if log_exp>=int(hero_level_data[e]["TotalExp"]) and log_exp=int(hero_level_data[e]["TotalExp"]) and pre_synth_xp= 100: + col_cost = col_cost * 10 + + col_cost = col_cost * 1000 + col_cost = max(100, int(col_cost / 1000)) + + self.logger.info(f"Synthesize {hero_exp} exp for hero {req.origin_user_hero_log_id}, costing {col_cost} col") + + await self.data.profile.add_col(req.user_id, -col_cost) + if synthesize_hero_log_data is not None: + resp.after_hero_log_user_data.append(HeroLogUserData.from_args(synthesize_hero_log_data)) + resp.after_hero_log_user_data[0].log_exp = remain_exp return resp.make() - async def handle_c816(self, header: SaoRequestHeader, request: bytes) -> bytes: + async def handle_c816(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: #custom/synthesize_enhancement_equipment req_data = SaoSynthesizeEnhancementEquipmentRequest(header, request) - synthesize_equipment_data = await self.data.item.get_user_equipment(req_data.user_id, req_data.origin_user_equipment_id) + resp = SaoSynthesizeEnhancementEquipmentResponse() + hero_level_data = self.load_data_csv("HeroLogLevel") + equip_level_data = self.load_data_csv("EquipmentLevel") + equipment_exp = 0 + col_cost = 0 for x in req_data.material_common_reward_user_data_list: - equipment_exp = 0 - itemList = await self.data.static.get_item_id(x.user_common_reward_id) - heroList = await self.data.static.get_hero_id(x.user_common_reward_id) - equipmentList = await self.data.static.get_equipment_id(x.user_common_reward_id) + if x.common_reward_type == RewardType.Item: + user_item = await self.data.item.get_user_item_by_id(x.user_common_reward_id) + static_item = await self.data.static.get_item_id(user_item['item_id']) + + if int(static_item['ItemTypeId']) == 7: + equipment_exp += int(static_item['Value']) + self.logger.info(f"Remove item {x.user_common_reward_id} and add {int(static_item['Value'])} XP (running {equipment_exp})") + await self.data.item.remove_item(x.user_common_reward_id) - if itemList: - equipment_exp = 2000 + int(synthesize_equipment_data["enhancement_exp"]) - await self.data.item.remove_item(req_data.user_id, x.user_common_reward_id) - - if equipmentList: - equipment_data = await self.data.item.get_user_equipment(req_data.user_id, x.user_common_reward_id) + elif x.common_reward_type == RewardType.Equipment: + equipment_data = await self.data.item.get_user_equipment_by_id(x.user_common_reward_id) if equipment_data is None: self.logger.error(f"Failed to find equipment {x.user_common_reward_id} for user {req_data.user_id}!") continue + + req_exp = 0 + for e in range(len(equip_level_data)): + if equipment_data['enhancement_value'] == int(equip_level_data[e]['EquipmentLevelId']): + req_exp = equip_level_data[e + 1]['RequireExp'] + break + + static_equip_data = await self.data.static.get_equipment_by_id(equipment_data['equipment_id']) + + equipment_exp += int(static_equip_data['CompositionExp']) + req_exp + self.logger.info(f"Remove equipment {x.user_common_reward_id} and add {int(static_equip_data['CompositionExp']) + req_exp} XP (running {equipment_exp})") + await self.data.item.remove_equipment(x.user_common_reward_id) - equipment_exp = int(equipment_data["enhancement_exp"]) + int(synthesize_equipment_data["enhancement_exp"]) - await self.data.item.remove_equipment(req_data.user_id, x.user_common_reward_id) - - if heroList: - hero_data = await self.data.item.get_hero_log(req_data.user_id, x.user_common_reward_id) + elif x.common_reward_type == RewardType.HeroLog: + hero_data = await self.data.item.get_hero_log_by_id(x.user_common_reward_id) if hero_data is None: self.logger.error(f"Failed to find hero {x.user_common_reward_id} for user {req_data.user_id}!") continue - equipment_exp = int(hero_data["log_exp"]) + int(synthesize_equipment_data["enhancement_exp"]) - await self.data.item.remove_hero_log(req_data.user_id, x.user_common_reward_id) - - if equipment_exp == 0: - self.logger.warn(f"Common reward {x.user_common_reward_id} (type {x.common_reward_type}) not found!") - continue + req_exp = 0 + for e in range(len(hero_level_data)): + if hero_data['log_level'] == int(hero_level_data[e]['HeroLogLevelId']): + req_exp = hero_level_data[e + 1]['RequireExp'] + break + + static_hero_data = await self.data.static.get_hero_by_id(hero_data['hero_log_id']) + + equipment_exp += int(static_hero_data['CompositionExp']) + req_exp + self.logger.info(f"Remove hero {x.user_common_reward_id} and add {int(static_hero_data['CompositionExp']) + req_exp} XP (running {equipment_exp})") + await self.data.item.remove_hero_log(x.user_common_reward_id) - await self.data.item.put_equipment_data(req_data.user_id, int(req_data.origin_user_equipment_id), synthesize_equipment_data["enhancement_value"], equipment_exp, 0, 0, 0) - - profile = await self.data.profile.get_profile(req_data.user_id) - new_col = int(profile["own_col"]) - 100 - - # Update profile - - await self.data.profile.put_profile( - req_data.user_id, - profile["user_type"], - profile["nick_name"], - profile["rank_num"], - profile["rank_exp"], - new_col, - profile["own_vp"], - profile["own_yui_medal"], - profile["setting_title_id"] - ) + else: + self.logger.warn(f"Unhandled ype {x.common_reward_type}! (running {equipment_exp})") + + equipment_exp = int(equipment_exp * 1.5) + await self.data.item.add_equipment_enhancement_exp(req_data.origin_user_equipment_id, equipment_exp) + synthesize_equipment_data = await self.data.item.get_user_equipment(req_data.user_id, req_data.origin_user_equipment_id) + equip_exp_new = synthesize_equipment_data['enhancement_exp'] + pre_synth_level = synthesize_equipment_data['enhancement_value'] + new_synth_level = 1 + + for e in range(len(equip_level_data)): + if equip_exp_new>=int(equip_level_data[e]["TotalExp"]) and equip_exp_new= 100: + col_cost = col_cost * 10 + + col_cost = col_cost * 1000 + col_cost = max(100, int(col_cost / 1000)) + + self.logger.info(f"Synthesize {equipment_exp} exp for equipment {req_data.origin_user_equipment_id}, costing {col_cost} col") + + await self.data.profile.add_col(req_data.user_id, -col_cost) + if synthesize_equipment_data is not None: + resp.after_equipment_user_data.append(EquipmentUserData.from_args(synthesize_equipment_data)) + resp.after_equipment_user_data[0].enhancement_exp = remain_exp + resp.after_equipment_user_data[0].enhancement_value = new_synth_level return resp.make() - async def handle_c806(self, header: SaoRequestHeader, request: bytes) -> bytes: + async def handle_c806(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: #custom/change_party req_data = SaoChangePartyRequest(header, request) - party_hero_list = [] - for party_team in req_data.party_data_list[0].party_team_data_list: - hero_data = await self.data.item.get_hero_log(req_data.user_id, party_team.user_hero_log_id) - hero_level = 1 - hero_exp = 0 + for party_team in req_data.party_data_list: + for hero in party_team.party_team_data_list: + await self.data.item.set_user_hero_weapons( + int(hero.user_hero_log_id), + int(hero.main_weapon_user_equipment_id) if hero.main_weapon_user_equipment_id and int(hero.main_weapon_user_equipment_id) > 0 else None, + int(hero.sub_equipment_user_equipment_id) if hero.sub_equipment_user_equipment_id and int(hero.sub_equipment_user_equipment_id) > 0 else None + ) + await self.data.item.set_user_hero_skills( + int(hero.user_hero_log_id), + hero.skill_slot1_skill_id if hero.skill_slot1_skill_id > 0 else None, + hero.skill_slot2_skill_id if hero.skill_slot2_skill_id > 0 else None, + hero.skill_slot3_skill_id if hero.skill_slot3_skill_id > 0 else None, + hero.skill_slot4_skill_id if hero.skill_slot4_skill_id > 0 else None, + hero.skill_slot5_skill_id if hero.skill_slot5_skill_id > 0 else None + ) - if hero_data: - hero_level = hero_data["log_level"] - hero_exp = hero_data["log_exp"] - await self.data.item.put_hero_log( + await self.data.item.put_hero_party( req_data.user_id, - party_team.user_hero_log_id, - hero_level, - hero_exp, - party_team.main_weapon_user_equipment_id, - party_team.sub_equipment_user_equipment_id, - party_team.skill_slot1_skill_id, - party_team.skill_slot2_skill_id, - party_team.skill_slot3_skill_id, - party_team.skill_slot4_skill_id, - party_team.skill_slot5_skill_id + party_team.team_no, + party_team.party_team_data_list[0].user_hero_log_id, + party_team.party_team_data_list[1].user_hero_log_id, + party_team.party_team_data_list[2].user_hero_log_id, ) - party_hero_list.append(party_team.user_hero_log_id) - - await self.data.item.put_hero_party(req_data.user_id, req_data.party_data_list[0].party_team_data_list[0].user_party_team_id, party_hero_list[0], party_hero_list[1], party_hero_list[2]) - - resp = SaoNoopResponse(header.cmd +1) + resp = SaoNoopResponse(GameconnectCmd.CHANGE_PARTY_RESPONSE) return resp.make() - async def handle_c904(self, header: SaoRequestHeader, request: bytes) -> bytes: + async def handle_c900(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + #quest/get_quest_scene_user_data_list + req = SaoGenericUserRequest(header, request) + + quest_data = await self.data.item.get_quest_logs(req.user_id) + + resp = SaoGetQuestSceneUserDataListResponse() + for quest in quest_data: + ex_bonus_data = await self.data.item.get_player_ex_bonus_by_quest(req.user_id, quest["quest_scene_id"]) + tmp = QuestSceneUserData.from_args(quest) + + for ex_bonus in ex_bonus_data: + tmp.quest_scene_ex_bonus_user_data_list.append(QuestSceneExBonusUserData.from_args(ex_bonus['ex_bonus_table_id'], ex_bonus['quest_clear_flag'])) + + resp.quest_scene_user_data_list.append(tmp) + return resp.make() + + async def handle_c902(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: # for whatever reason, having all entries empty or filled changes nothing + #quest/get_quest_scene_prev_scan_profile_card + resp = SaoGetQuestScenePrevScanProfileCardResponse() + resp.profile_card_data.append(ReadProfileCardData.from_args({}, {})) + return resp.make() + + async def handle_c904(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: #quest/episode_play_start req_data = SaoEpisodePlayStartRequest(header, request) user_id = req_data.user_id profile_data = await self.data.profile.get_profile(user_id) - await self.data.item.create_session( + sesh_id = await self.data.item.create_session( user_id, int(req_data.play_start_request_data[0].user_party_id), req_data.episode_id, @@ -450,178 +1175,228 @@ class SaoBase: req_data.play_start_request_data[0].quest_drop_boost_apply_flag ) - resp = SaoEpisodePlayStartResponse(header.cmd +1, profile_data) + resp = SaoEpisodePlayStartResponse() + resp.play_start_response_data.append(QuestScenePlayStartResponseData.from_args(sesh_id, profile_data['nick_name'])) + resp.multi_play_start_response_data.append(QuestSceneMultiPlayStartResponseData.from_args()) return resp.make() - async def handle_c908(self, header: SaoRequestHeader, request: bytes) -> bytes: # Level calculation missing for the profile and heroes + async def handle_c908(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: # Level calculation missing for the profile and heroes #quest/episode_play_end - req_data = SaoEpisodePlayEndRequest(header, request) + resp = SaoEpisodePlayEndResponse() - # Add stage progression to database user_id = req_data.user_id episode_id = req_data.episode_id - quest_clear_flag = bool(req_data.play_end_request_data_list[0].score_data_list[0].boss_destroying_num) - clear_time = req_data.play_end_request_data_list[0].score_data_list[0].clear_time - combo_num = req_data.play_end_request_data_list[0].score_data_list[0].combo_num - total_damage = req_data.play_end_request_data_list[0].score_data_list[0].total_damage - concurrent_destroying_num = req_data.play_end_request_data_list[0].score_data_list[0].concurrent_destroying_num + play_end = req_data.play_end_request_data[0] + base_get_data = play_end.base_get_data[0] + score_data = play_end.score_data[0] + exp = 0 + cleared_mission_ct = 0 + highest_mission_diff_cleared = 0 + num_monsters_defeated = 0 + monsters_defeated_data = {} + json_data = {"data": []} + + ep_data = await self.data.static.get_episode_by_id(req_data.episode_id) + quest_scene = await self.data.static.get_quest_by_id(int(ep_data['QuestSceneId'])) + reward_table = await self.data.static.get_rewards_by_subtable(int(quest_scene['RewardTableSubId'])) + ex_bonus_table = await self.data.static.get_ex_bonuses_by_subtable(int(ep_data['ExBonusTableSubId'])) - profile = await self.data.profile.get_profile(user_id) - vp = int(profile["own_vp"]) - exp = int(profile["rank_exp"]) + 100 #always 100 extra exp for some reason - col = int(profile["own_col"]) + int(req_data.play_end_request_data_list[0].base_get_data_list[0].get_col) + await self.data.profile.add_col(user_id, base_get_data.get_col) + quest_clear_flag = score_data.clear_time > 0 + clear_time = score_data.clear_time + combo_num = score_data.combo_num + total_damage = score_data.total_damage + concurrent_destroying_num = score_data.concurrent_destroying_num + if quest_clear_flag is True: - # Save stage progression - to be revised to avoid saving worse score - - # Reference Episode.csv but Chapter 2,3,4 and 5 reports id -1, match using /10 + last digits - if episode_id > 10000 and episode_id < 11000: - # Starts at 1001 - episode_id = episode_id - 9000 - elif episode_id > 20000: - # Starts at 2001 - stage_id = str(episode_id)[-2:] - episode_id = episode_id / 10 - episode_id = int(episode_id) + int(stage_id) - - # Match episode_id with the questSceneId saved in the DB through sortNo - questId = await self.data.static.get_quests_id(episode_id) - episode_id = questId[2] - - await self.data.item.put_player_quest(user_id, episode_id, quest_clear_flag, clear_time, combo_num, total_damage, concurrent_destroying_num) - - vp = int(profile["own_vp"]) + 10 #always 10 VP per cleared stage - - + await self.data.profile.add_vp(user_id, quest_scene['SingleRewardVp']) + await self.data.profile.add_exp(user_id, quest_scene['SuccessPlayerExp']) + exp = await self.data.profile.get_exp(user_id) + + else: + await self.data.profile.add_exp(user_id, quest_scene['FailedPlayerExp']) + exp = await self.data.profile.get_exp(user_id) + # Calculate level based off experience and the CSV list - with open(r'titles/sao/data/PlayerRank.csv') as csv_file: - csv_reader = csv.reader(csv_file, delimiter=',') - line_count = 0 - data = [] - rowf = False - for row in csv_reader: - if rowf==False: - rowf=True - else: - data.append(row) + player_level_data = self.load_data_csv("PlayerRank") - for i in range(0,len(data)): - if exp>=int(data[i][1]) and exp=int(player_level_data[i]["TotalExp"]) and exp highest_mission_diff_cleared: + highest_mission_diff_cleared = mission.mission_difficulty_id + + for monster_data in play_end.discovery_enemy_data_list: + num_monsters_defeated += monster_data.destroy_num + monsters_defeated_data[monster_data.enemy_kind_id] = monster_data.destroy_num + + for bonus in ex_bonus_table: + table_id = int(bonus['ExBonusTableId']) + ach_thing = 0 + condition = int(bonus['ExBonusConditionId']) + val1 = int(bonus['ConditionValue1']) + val2 = int(bonus['ConditionValue2']) + if condition == ExBonusCondition.CLEAR_UNDER_X_SECS: + if quest_clear_flag and int(score_data.clear_time / 1000) < val1: + await self.add_reward(bonus._asdict(), user_id) + await self.data.item.put_ex_bonus(user_id, int(ep_data['QuestSceneId']), table_id, True) + ach_thing = 2 + elif condition == ExBonusCondition.DEFEAT_X_MONSTER_Y_TIMES: + if monsters_defeated_data.get(val1, 0) >= val2: + await self.add_reward(bonus._asdict(), user_id) + await self.data.item.put_ex_bonus(user_id, int(ep_data['QuestSceneId']), table_id, True) + ach_thing = 2 + elif condition == ExBonusCondition.DEFEAT_X_MONSTERS: + if num_monsters_defeated >= val1: + await self.add_reward(bonus._asdict(), user_id) + await self.data.item.put_ex_bonus(user_id, int(ep_data['QuestSceneId']), table_id, True) + ach_thing = 2 + elif condition == ExBonusCondition.CLEAR_X_MISSIONS: + if cleared_mission_ct >= val1: + await self.add_reward(bonus._asdict(), user_id) + await self.data.item.put_ex_bonus(user_id, int(ep_data['QuestSceneId']), table_id, True) + ach_thing = 2 + elif condition == ExBonusCondition.CLEAR_MISSION_DIFFICULTY_X: + if highest_mission_diff_cleared >= val1: + await self.add_reward(bonus._asdict(), user_id) + await self.data.item.put_ex_bonus(user_id, int(ep_data['QuestSceneId']), table_id, True) + ach_thing = 2 + elif condition == ExBonusCondition.COLLECT_X_LOGS: + if len(play_end.get_unanalyzed_log_tmp_reward_data_list) >= val1: + await self.add_reward(bonus._asdict(), user_id) + await self.data.item.put_ex_bonus(user_id, int(ep_data['QuestSceneId']), table_id, True) + ach_thing = 2 + elif condition == ExBonusCondition.CLEAR_SKILL_LEVEL_X: + if score_data.reaching_skill_level >= val1: + await self.add_reward(bonus._asdict(), user_id) + await self.data.item.put_ex_bonus(user_id, int(ep_data['QuestSceneId']), table_id, True) + ach_thing = 2 + elif condition == ExBonusCondition.NO_LOSSES: + if score_data.total_loss_num == 0: + await self.add_reward(bonus._asdict(), user_id) + await self.data.item.put_ex_bonus(user_id, int(ep_data['QuestSceneId']), table_id, True) + ach_thing = 2 + elif condition == ExBonusCondition.ACCEL_X_TIMES: + if score_data.acceleration_invocation_num >= val1: + await self.add_reward(bonus._asdict(), user_id) + await self.data.item.put_ex_bonus(user_id, int(ep_data['QuestSceneId']), table_id, True) + ach_thing = 2 + elif condition == ExBonusCondition.MAX_COMBO_X: + if score_data.combo_num >= val1: + await self.add_reward(bonus._asdict(), user_id) + await self.data.item.put_ex_bonus(user_id, int(ep_data['QuestSceneId']), table_id, True) + ach_thing = 2 + elif condition == ExBonusCondition.MULTIPLAYER_CLEAR_X: + # TODO + pass + else: + self.logger.warn(f"Unhandled EX Bonus condition {condition}") + + resp.play_end_response_data[0].ex_bonus_data_list.append(QuestScenePlayEndExBonusData.from_args(table_id, ach_thing)) + + self.logger.info(f"User {user_id} {'cleared' if quest_clear_flag else 'ended'} episode {episode_id}") + + for rare_drop in play_end.get_rare_drop_data_list: + rewardList = self.load_data_csv("QuestRareDrop") + for drop in rewardList: + if int(drop['QuestRareDropId']) == rare_drop.quest_rare_drop_id: + await self.add_reward(drop, user_id) + break + + for unanalyzed_log in play_end.get_unanalyzed_log_tmp_reward_data_list: + able_rewards: List[Row] = [] + for reward in reward_table: + if int(reward['UnanalyzedLogGradeId']) == unanalyzed_log.unanalyzed_log_grade_id: + able_rewards.append(reward) + randomized_unanalyzed_id = choice(able_rewards) + + await self.add_reward(randomized_unanalyzed_id._asdict(), user_id) + json_data["data"].append(randomized_unanalyzed_id._asdict()) + + + trace_table = await self.data.static.get_player_trace_by_subid(quest_scene['PlayerTraceTableSubId']) + + for trace in play_end.get_player_trace_data_list: + self.logger.info(f"User {user_id} obtained trace {trace.user_quest_scene_player_trace_id}") + resp.play_end_response_data[0].play_end_player_trace_reward_data_list.append(QuestScenePlayEndPlayerTraceRewardData.from_args(choice(trace_table)._asdict())) + + await self.data.item.create_end_session(user_id, ep_data['QuestSceneId'], play_end.play_result_flag, json_data["data"]) # Update heroes from the used party play_session = await self.data.item.get_session(user_id) - session_party = await self.data.item.get_hero_party(user_id, play_session["user_party_team_id"]) + session_party = await self.data.item.get_hero_party_by_id(play_session["user_party_team_id"]) + if session_party: + hero_level_data = self.load_data_csv("HeroLogLevel") + hero_list = [] + hero_list.append(session_party["user_hero_log_id_1"]) + hero_list.append(session_party["user_hero_log_id_2"]) + hero_list.append(session_party["user_hero_log_id_3"]) - hero_list = [] - hero_list.append(session_party["user_hero_log_id_1"]) - hero_list.append(session_party["user_hero_log_id_2"]) - hero_list.append(session_party["user_hero_log_id_3"]) + for i in range(0,3): + await self.data.item.add_hero_xp(hero_list[i], base_get_data.get_hero_log_exp) + log_exp = await self.data.item.get_hero_xp(hero_list[i]) - for i in range(0,len(hero_list)): - hero_data = await self.data.item.get_hero_log(user_id, hero_list[i]) + # Calculate hero level based off experience and the CSV list + if log_exp: + for e in range(0,len(hero_level_data)): + if log_exp>=int(hero_level_data[e]["TotalExp"]) and log_exp=int(data[e][1]) and log_exp bytes: + async def handle_c90a(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + #quest/episode_play_end_unanalyzed_log_fixed + req = SaoEpisodePlayEndUnanalyzedLogFixedRequest(header, request) + resp = SaoEpisodePlayEndUnanalyzedLogFixedResponse() + + end_session_data = await self.data.item.get_end_session(req.user_id) + for data in end_session_data['reward_data']: + resp.play_end_unanalyzed_log_reward_data_list.append(QuestScenePlayEndUnanalyzedLogRewardData.from_args(data['UnanalyzedLogGradeId'], data)) + + return resp.make() + + async def handle_c914(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: #quest/trial_tower_play_start req_data = SaoTrialTowerPlayStartRequest(header, request) user_id = req_data.user_id - floor_id = req_data.trial_tower_id profile_data = await self.data.profile.get_profile(user_id) - await self.data.item.create_session( + sesh_id = await self.data.item.create_session( user_id, int(req_data.play_start_request_data[0].user_party_id), req_data.trial_tower_id, @@ -629,374 +1404,1222 @@ class SaoBase: req_data.play_start_request_data[0].quest_drop_boost_apply_flag ) - resp = SaoEpisodePlayStartResponse(header.cmd +1, profile_data) + self.logger.info(f"User {req_data.user_id} started trial tower on floor {req_data.trial_tower_id}") + resp = SaoTrialTowerPlayStartResponse(sesh_id, profile_data['nick_name']) return resp.make() - async def handle_c918(self, header: SaoRequestHeader, request: bytes) -> bytes: + async def handle_c918(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: #quest/trial_tower_play_end req_data = SaoTrialTowerPlayEndRequest(header, request) + resp = SaoTrialTowerPlayEndResponse() - # Add tower progression to database user_id = req_data.user_id trial_tower_id = req_data.trial_tower_id - next_tower_id = 0 - quest_clear_flag = bool(req_data.play_end_request_data_list[0].score_data_list[0].boss_destroying_num) - clear_time = req_data.play_end_request_data_list[0].score_data_list[0].clear_time - combo_num = req_data.play_end_request_data_list[0].score_data_list[0].combo_num - total_damage = req_data.play_end_request_data_list[0].score_data_list[0].total_damage - concurrent_destroying_num = req_data.play_end_request_data_list[0].score_data_list[0].concurrent_destroying_num - - if quest_clear_flag is True: - # Save tower progression - to be revised to avoid saving worse score - if trial_tower_id == 9: - next_tower_id = 10001 - elif trial_tower_id == 10: - trial_tower_id = 10001 - next_tower_id = 3011 - elif trial_tower_id == 19: - next_tower_id = 10002 - elif trial_tower_id == 20: - trial_tower_id = 10002 - next_tower_id = 3021 - elif trial_tower_id == 29: - next_tower_id = 10003 - elif trial_tower_id == 30: - trial_tower_id = 10003 - next_tower_id = 3031 - elif trial_tower_id == 39: - next_tower_id = 10004 - elif trial_tower_id == 40: - trial_tower_id = 10004 - next_tower_id = 3041 - elif trial_tower_id == 49: - next_tower_id = 10005 - elif trial_tower_id == 50: - trial_tower_id = 10005 - next_tower_id = 3051 - else: - trial_tower_id = trial_tower_id + 3000 - next_tower_id = trial_tower_id + 1 - - await self.data.item.put_player_quest(user_id, trial_tower_id, quest_clear_flag, clear_time, combo_num, total_damage, concurrent_destroying_num) - - # Check if next stage is already done - checkQuest = await self.data.item.get_quest_log(user_id, next_tower_id) - if not checkQuest: - if next_tower_id != 3101: - await self.data.item.put_player_quest(user_id, next_tower_id, 0, 0, 0, 0, 0) - - # Update the profile - profile = await self.data.profile.get_profile(user_id) + play_end = req_data.play_end_request_data[0] + base_get_data = play_end.base_get_data[0] + score_data = play_end.score_data[0] + exp = 0 + cleared_mission_ct = 0 + highest_mission_diff_cleared = 0 + num_monsters_defeated = 0 + monsters_defeated_data = {} + json_data = {"data": []} - exp = int(profile["rank_exp"]) + 100 #always 100 extra exp for some reason - col = int(profile["own_col"]) + int(req_data.play_end_request_data_list[0].base_get_data_list[0].get_col) + ep_data = await self.data.static.get_tower_by_id(trial_tower_id) + quest_scene = await self.data.static.get_quest_by_id(int(ep_data['QuestSceneId'])) + reward_table = await self.data.static.get_rewards_by_subtable(int(quest_scene['RewardTableSubId'])) + ex_bonus_table = await self.data.static.get_ex_bonuses_by_subtable(int(ep_data['ExBonusTableSubId'])) + await self.data.profile.add_col(user_id, base_get_data.get_col) + + quest_clear_flag = score_data.clear_time > 0 + clear_time = score_data.clear_time + combo_num = score_data.combo_num + total_damage = score_data.total_damage + concurrent_destroying_num = score_data.concurrent_destroying_num + + if quest_clear_flag is True: + await self.data.profile.add_vp(user_id, quest_scene['SingleRewardVp']) + await self.data.profile.add_exp(user_id, quest_scene['SuccessPlayerExp']) + exp = await self.data.profile.get_exp(user_id) + + else: + await self.data.profile.add_exp(user_id, quest_scene['FailedPlayerExp']) + exp = await self.data.profile.get_exp(user_id) + # Calculate level based off experience and the CSV list - with open(r'titles/sao/data/PlayerRank.csv') as csv_file: - csv_reader = csv.reader(csv_file, delimiter=',') - line_count = 0 - data = [] - rowf = False - for row in csv_reader: - if rowf==False: - rowf=True - else: - data.append(row) + player_level_data = self.load_data_csv("PlayerRank") - for i in range(0,len(data)): - if exp>=int(data[i][1]) and exp=int(player_level_data[i]["TotalExp"]) and exp highest_mission_diff_cleared: + highest_mission_diff_cleared = mission.mission_difficulty_id + + for monster_data in play_end.discovery_enemy_data_list: + num_monsters_defeated += monster_data.destroy_num + monsters_defeated_data[monster_data.enemy_kind_id] = monster_data.destroy_num + + for bonus in ex_bonus_table: + table_id = int(bonus['ExBonusTableId']) + ach_thing = 0 + condition = int(bonus['ExBonusConditionId']) + val1 = int(bonus['ConditionValue1']) + val2 = int(bonus['ConditionValue2']) + if condition == ExBonusCondition.CLEAR_UNDER_X_SECS: + if quest_clear_flag and int(score_data.clear_time / 1000) < val1: + await self.add_reward(bonus._asdict(), user_id) + await self.data.item.put_ex_bonus(user_id, int(ep_data['QuestSceneId']), table_id, True) + ach_thing = 2 + elif condition == ExBonusCondition.DEFEAT_X_MONSTER_Y_TIMES: + if monsters_defeated_data.get(val1, 0) >= val2: + await self.add_reward(bonus._asdict(), user_id) + await self.data.item.put_ex_bonus(user_id, int(ep_data['QuestSceneId']), table_id, True) + ach_thing = 2 + elif condition == ExBonusCondition.DEFEAT_X_MONSTERS: + if num_monsters_defeated >= val1: + await self.add_reward(bonus._asdict(), user_id) + await self.data.item.put_ex_bonus(user_id, int(ep_data['QuestSceneId']), table_id, True) + ach_thing = 2 + elif condition == ExBonusCondition.CLEAR_X_MISSIONS: + if cleared_mission_ct >= val1: + await self.add_reward(bonus._asdict(), user_id) + await self.data.item.put_ex_bonus(user_id, int(ep_data['QuestSceneId']), table_id, True) + ach_thing = 2 + elif condition == ExBonusCondition.CLEAR_MISSION_DIFFICULTY_X: + if highest_mission_diff_cleared >= val1: + await self.add_reward(bonus._asdict(), user_id) + await self.data.item.put_ex_bonus(user_id, int(ep_data['QuestSceneId']), table_id, True) + ach_thing = 2 + elif condition == ExBonusCondition.COLLECT_X_LOGS: + if len(play_end.get_unanalyzed_log_tmp_reward_data_list) >= val1: + await self.add_reward(bonus._asdict(), user_id) + await self.data.item.put_ex_bonus(user_id, int(ep_data['QuestSceneId']), table_id, True) + ach_thing = 2 + elif condition == ExBonusCondition.CLEAR_SKILL_LEVEL_X: + if score_data.reaching_skill_level >= val1: + await self.add_reward(bonus._asdict(), user_id) + await self.data.item.put_ex_bonus(user_id, int(ep_data['QuestSceneId']), table_id, True) + ach_thing = 2 + elif condition == ExBonusCondition.NO_LOSSES: + if score_data.total_loss_num == 0: + await self.add_reward(bonus._asdict(), user_id) + await self.data.item.put_ex_bonus(user_id, int(ep_data['QuestSceneId']), table_id, True) + ach_thing = 2 + elif condition == ExBonusCondition.ACCEL_X_TIMES: + if score_data.acceleration_invocation_num >= val1: + await self.add_reward(bonus._asdict(), user_id) + await self.data.item.put_ex_bonus(user_id, int(ep_data['QuestSceneId']), table_id, True) + ach_thing = 2 + elif condition == ExBonusCondition.MAX_COMBO_X: + if score_data.combo_num >= val1: + await self.add_reward(bonus._asdict(), user_id) + await self.data.item.put_ex_bonus(user_id, int(ep_data['QuestSceneId']), table_id, True) + ach_thing = 2 + elif condition == ExBonusCondition.MULTIPLAYER_CLEAR_X: + # TODO + pass + else: + self.logger.warn(f"Unhandled EX Bonus condition {condition}") + + resp.play_end_response_data[0].ex_bonus_data_list.append(QuestScenePlayEndExBonusData.from_args(table_id, ach_thing)) + + self.logger.info(f"User {user_id} {'cleared' if quest_clear_flag else 'ended'} trial tower {trial_tower_id}") + + for rare_drop in play_end.get_rare_drop_data_list: + rewardList = self.load_data_csv("QuestRareDrop") + for drop in rewardList: + if int(drop['QuestRareDropId']) == rare_drop.quest_rare_drop_id: + await self.add_reward(drop, user_id) + break + + for unanalyzed_log in play_end.get_unanalyzed_log_tmp_reward_data_list: + able_rewards: List[Row] = [] + for reward in reward_table: + if int(reward['UnanalyzedLogGradeId']) == unanalyzed_log.unanalyzed_log_grade_id: + able_rewards.append(reward) + randomized_unanalyzed_id = choice(able_rewards) + + await self.add_reward(randomized_unanalyzed_id._asdict(), user_id) + json_data["data"].append(randomized_unanalyzed_id._asdict()) + + + trace_table = await self.data.static.get_player_trace_by_subid(quest_scene['PlayerTraceTableSubId']) + + for trace in play_end.get_player_trace_data_list: + self.logger.info(f"User {user_id} obtained trace {trace.user_quest_scene_player_trace_id}") + resp.play_end_response_data[0].play_end_player_trace_reward_data_list.append(QuestScenePlayEndPlayerTraceRewardData.from_args(choice(trace_table)._asdict())) + + await self.data.item.create_end_session(user_id, ep_data['QuestSceneId'], play_end.play_result_flag, json_data["data"]) # Update heroes from the used party play_session = await self.data.item.get_session(user_id) - session_party = await self.data.item.get_hero_party(user_id, play_session["user_party_team_id"]) + session_party = await self.data.item.get_hero_party_by_id(play_session["user_party_team_id"]) + if session_party: + hero_level_data = self.load_data_csv("HeroLogLevel") + hero_list = [] + hero_list.append(session_party["user_hero_log_id_1"]) + hero_list.append(session_party["user_hero_log_id_2"]) + hero_list.append(session_party["user_hero_log_id_3"]) - hero_list = [] - hero_list.append(session_party["user_hero_log_id_1"]) - hero_list.append(session_party["user_hero_log_id_2"]) - hero_list.append(session_party["user_hero_log_id_3"]) + for i in range(0,3): + self.logger.info(f"Give hero {hero_list[i]} {base_get_data.get_hero_log_exp}") + await self.data.item.add_hero_xp(hero_list[i], base_get_data.get_hero_log_exp) + log_exp = await self.data.item.get_hero_xp(hero_list[i]) - for i in range(0,len(hero_list)): - hero_data = await self.data.item.get_hero_log(user_id, hero_list[i]) + # Calculate hero level based off experience and the CSV list + if log_exp: + for e in range(0,len(hero_level_data)): + if log_exp>=int(hero_level_data[e]["TotalExp"]) and log_exp=int(data[e][1]) and log_exp bytes: - #quest/episode_play_end_unanalyzed_log_fixed - - req = SaoEpisodePlayEndUnanalyzedLogFixedRequest(header, request) - - end_session_data = await self.data.item.get_end_session(req.user_id) - - resp = SaoEpisodePlayEndUnanalyzedLogFixedResponse(header.cmd +1, end_session_data[4]) - return resp.make() - - async def handle_c91a(self, header: SaoRequestHeader, request: bytes) -> bytes: # handler is identical to the episode + async def handle_c91a(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: # handler is identical to the episode #quest/trial_tower_play_end_unanalyzed_log_fixed - req = TrialTowerPlayEndUnanalyzedLogFixed(header, request) - + req = SaoTrialTowerPlayEndUnanalyzedLogFixedRequest(header, request) + resp = SaoTrialTowerPlayEndUnanalyzedLogFixedResponse() + end_session_data = await self.data.item.get_end_session(req.user_id) + for data in end_session_data['reward_data']: + resp.play_end_unanalyzed_log_reward_data_list.append(QuestScenePlayEndUnanalyzedLogRewardData.from_args(data['UnanalyzedLogGradeId'], data)) - resp = SaoEpisodePlayEndUnanalyzedLogFixedResponse(header.cmd +1, end_session_data[4]) return resp.make() - async def handle_cd00(self, header: SaoRequestHeader, request: bytes) -> bytes: + async def handle_c930(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # quest/get_chat_side_story_user_data_list + req = SaoGenericUserRequest(header, request) + resp = SaoGetChatSideStoryUserDataListResponse() + + return resp.make() + + async def handle_ca02(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + #quest_multi_play_room/get_quest_scene_multi_play_photon_server + resp = SaoGetQuestSceneMultiPlayPhotonServerResponse(self.game_cfg.server.photon_app_id) + return resp.make() + + async def handle_cb02(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # quest_ranking/get_quest_hierarchy_progress_degrees_ranking_list + req = SaoGetQuestHierarchyProgressDegreesRankingListRequest(header, request) + return SaoGetQuestHierarchyProgressDegreesRankingListResponse(GameconnectCmd.GET_QUEST_HIERARCHY_PROGRESS_DEGREES_RANKING_LIST_RESPONSE).make() + + async def handle_cb04(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # quest_ranking/get_quest_popular_hero_log_ranking_list + req = SaoGetQuestPopularHeroLogRankingListRequest(header, request) + return SaoGetQuestPopularHeroLogRankingListResponse(GameconnectCmd.GET_QUEST_POPULAR_HERO_LOG_RANKING_LIST_RESPONSE).make() + + async def handle_cd00(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: #defrag_match/get_defrag_match_basic_data - resp = SaoGetDefragMatchBasicDataResponse(header.cmd +1) + resp = SaoGetDefragMatchBasicDataResponse() + data = DefragMatchBasicUserData.from_args() return resp.make() - async def handle_cd02(self, header: SaoRequestHeader, request: bytes) -> bytes: + async def handle_cd02(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: #defrag_match/get_defrag_match_ranking_user_data - resp = SaoGetDefragMatchRankingUserDataResponse(header.cmd +1) + # TODO: League points + req = SaoGetDefragMatchRankingUserDataRequest(header, request) + profile = await self.data.profile.get_profile(req.user_id) + resp = SaoGetDefragMatchRankingUserDataResponse(profile._asdict()) return resp.make() - async def handle_cd04(self, header: SaoRequestHeader, request: bytes) -> bytes: + async def handle_cd04(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: #defrag_match/get_defrag_match_league_point_ranking_list - resp = SaoGetDefragMatchLeaguePointRankingListResponse(header.cmd +1) + resp = SaoGetDefragMatchLeaguePointRankingListResponse() return resp.make() - async def handle_cd06(self, header: SaoRequestHeader, request: bytes) -> bytes: + async def handle_cd06(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: #defrag_match/get_defrag_match_league_score_ranking_list - resp = SaoGetDefragMatchLeagueScoreRankingListResponse(header.cmd +1) + resp = SaoGetDefragMatchLeagueScoreRankingListResponse() return resp.make() - async def handle_d404(self, header: SaoRequestHeader, request: bytes) -> bytes: - #other/bnid_serial_code_check - resp = SaoBnidSerialCodeCheckResponse(header.cmd +1) - return resp.make() - - async def handle_c306(self, header: SaoRequestHeader, request: bytes) -> bytes: - #card/scan_qr_quest_profile_card - resp = SaoScanQrQuestProfileCardResponse(header.cmd +1) - return resp.make() - - async def handle_c700(self, header: SaoRequestHeader, request: bytes) -> bytes: - # shop/get_shop_resource_sales_data_list - # TODO: Get user shop data - req = GetShopResourceSalesDataListRequest(header, request) - resp = GetShopResourceSalesDataListResponse(header.cmd + 1) - return resp.make() - - async def handle_d100(self, header: SaoRequestHeader, request: bytes) -> bytes: - # shop/get_yui_medal_shop_user_data_list - # TODO: Get user shop data - req = GetYuiMedalShopUserDataListRequest(header, request) - resp = GetYuiMedalShopUserDataListResponse(header.cmd + 1) - return resp.make() - - async def handle_cf0e(self, header: SaoRequestHeader, request: bytes) -> bytes: + async def handle_cf0e(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: # gasha/get_gasha_medal_shop_user_data_list # TODO: Get user shop data req = GetGashaMedalShopUserDataListRequest(header, request) - resp = GetGashaMedalShopUserDataListResponse(header.cmd + 1) + resp = GetGashaMedalShopUserDataListResponse(GameconnectCmd.GET_GASHA_MEDAL_SHOP_USER_DATA_LIST_RESPONSE) return resp.make() - - async def handle_d5da(self, header: SaoRequestHeader, request: bytes) -> bytes: + + async def handle_d000(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + req = SaoGenericUserRequest(header, request) + resp = SaoGetAdventureExecUserDataResponse() + return resp.make() + + async def handle_d100(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # shop/get_yui_medal_shop_user_data_list + # TODO: Get user shop data + req = GetYuiMedalShopUserDataListRequest(header, request) + resp = GetYuiMedalShopUserDataListResponse(GameconnectCmd.GET_YUI_MEDAL_SHOP_USER_DATA_LIST_RESPONSE) + return resp.make() + + async def handle_d200(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # mission/get_beginner_mission_user_data + req = SaoGetBeginnerMissionUserDataRequest(header, request) + resp = SaoGetBeginnerMissionUserDataResponse() + profile = await self.data.profile.get_profile(req.user_id) + if profile: + if profile['ad_confirm_date']: + resp.data[0].ad_confirm_date = profile['ad_confirm_date'] + resp.data[0].ad_confirm_flag = 1 + + return resp.make() + + async def handle_d202(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # mission/get_beginner_mission_progresses_user_data_list + req = SaoGetBeginnerMissionProgressesUserDataListRequest(header, request) + resp = SaoGetBeginnerMissionProgressesUserDataListResponse() + + return resp.make() + + async def handle_d204(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # mission/get_beginner_mission_seat_progresses_user_data_list + req = SaoGetBeginnerMissionSeatProgressesUserDataListRequest(header, request) + resp = SaoGetBeginnerMissionSeatProgressesUserDataListResponse() + + return resp.make() + + async def handle_d206(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # mission/beginner_mission_ad_confirm_notification + req = SaoBeginnerMissionAdConfirmNotificationRequest(header, request) + self.logger.info(f"User {req.user_id} confirmed ad for beginner mission {req.beginner_mission_id}") + await self.data.profile.update_beginner_mission_date(req.user_id) + + return SaoNoopResponse(GameconnectCmd.BEGINNER_MISSION_AD_CONFIRM_NOTIFICATION_RESPONSE).make() + + async def handle_d312(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # campaign/get_linked_site_reg_campaign_user_data + req = SaoGetLinkedSiteRegCampaignUserDataRequest(header, request) + resp = SaoGetLinkedSiteRegCampaignUserDataResponse() + + return resp.make() + + async def handle_d400(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # other/get_hero_log_unit_user_data_list + req = SaoGenericUserRequest(header, request) + resp = SaoGetHeroLogUnitUserDataListResponse() + + return resp.make() + + async def handle_d402(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # other/get_chara_unit_user_data_list + req = SaoGenericUserRequest(header, request) + resp = SaoGetCharaUnitUserDataListResponse() + + return resp.make() + + async def handle_d404(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # other/bnid_serial_code_check + req = SaoBnidSerialCodeCheckRequest() + resp = SaoBnidSerialCodeCheckResponse() + return resp.make() + + async def handle_d404(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # other/bnid_serial_code_entry_by_appendix_card + req = SaoBnidSerialCodeEntryByAppendixCardRequest() + resp = SaoBnidSerialCodeEntryByAppendixCardResponse() + return resp.make() + + async def handle_d500(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_player_ranks + tbl = self.load_data_csv('PlayerRank') + resp = SaoGetMPlayerRanksResponse(tbl) + return resp.make() + + async def handle_d502(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_titles + tbl = self.load_data_csv('Title') + resp = SaoGetMTitlesResponse(tbl) + return resp.make() + + async def handle_d504(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_fragments + tbl = self.load_data_csv('Fragment') + resp = SaoGetMFragmentsResponse(tbl) + return resp.make() + + async def handle_d506(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_reward_tables + tbl = self.load_data_csv('RewardTable') + resp = SaoGetMRewardTablesResponse(tbl) + return resp.make() + + async def handle_d508(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_reward_sets + tbl = self.load_data_csv('RewardSet') + resp = SaoGetMRewardSetsResponse(tbl) + return resp.make() + + async def handle_d50a(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_unanalyzed_log_grades + tbl = self.load_data_csv('UnanalyzedLogGrade') + resp = SaoGetMUnanalyzedLogGradesResponse(tbl) + return resp.make() + + async def handle_d50c(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_appoint_leader_params + tbl = self.load_data_csv('AppointLeaderParam') + resp = SaoGetMAppointLeaderParamsResponse(tbl) + return resp.make() + + async def handle_d50e(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_appoint_leader_effects + tbl = self.load_data_csv('AppointLeaderEffect') + resp = SaoGetMAppointLeaderEffectsResponse(tbl) + return resp.make() + + async def handle_d510(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_appoint_leader_effect_types + tbl = self.load_data_csv('AppointLeaderEffectType') + resp = SaoGetMAppointLeaderEffectTypesResponse(tbl) + return resp.make() + + async def handle_d512(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_rarities + tbl = self.load_data_csv('Rarity') + resp = SaoGetMRaritiesResponse(tbl) + return resp.make() + + async def handle_d514(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_composition_events + tbl = self.load_data_csv('CompositionEvent') + resp = SaoGetMCompositionEventsResponse(tbl) + return resp.make() + + async def handle_d516(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_composition_params + tbl = self.load_data_csv('CompositionParam') + resp = SaoGetMCompositionParamsResponse(tbl) + return resp.make() + + async def handle_d518(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_game_play_prices + tbl = self.load_data_csv('GamePlayPrice') + resp = SaoGetMGamePlayPricesResponse(tbl) + return resp.make() + + async def handle_d51a(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_buy_tickets + tbl = self.load_data_csv('BuyTicket') + resp = SaoGetMBuyTicketsResponse(tbl) + return resp.make() + + async def handle_d51c(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_tips + tbl = self.load_data_csv('Tips') + resp = SaoGetMTipsResponse(tbl) + return resp.make() + + async def handle_d51e(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_caps + tbl = self.load_data_csv('Cap') + resp = SaoGetMCapsResponse(tbl) + return resp.make() + + async def handle_d520(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_hero_log + tbl = self.load_data_csv('HeroLog') + resp = SaoGetMHeroLogResponse(tbl) + return resp.make() + + async def handle_d522(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_hero_log_levels + tbl = self.load_data_csv('HeroLogLevel') + resp = SaoGetMHeroLogLevelsResponse(tbl) + return resp.make() + + async def handle_d524(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_hero_log_roles + tbl = self.load_data_csv('HeroLogRole') + resp = SaoGetMHeroLogRolesResponse(tbl) + return resp.make() + + async def handle_d526(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_hero_log_trust_ranks + tbl = self.load_data_csv('HeroLogTrustRank') + resp = SaoGetMHeroLogTrustRanksResponse(tbl) + return resp.make() + + async def handle_d528(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_charas + tbl = self.load_data_csv('Chara') + resp = SaoGetMCharasResponse(tbl) + return resp.make() + + async def handle_d52a(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_chara_friendly_ranks + tbl = self.load_data_csv('CharaFriendlyRank') + resp = SaoGetMCharaFriendlyRanksResponse(tbl) + return resp.make() + + async def handle_d52c(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_equipments + tbl = self.load_data_csv('Equipment') + resp = SaoGetMEquipmentsResponse(tbl) + return resp.make() + + async def handle_d52e(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_equipment_levels + tbl = self.load_data_csv('EquipmentLevel') + resp = SaoGetMEquipmentLevelsResponse(tbl) + return resp.make() + + async def handle_d530(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_weapon_types + tbl = self.load_data_csv('WeaponType') + resp = SaoGetMWeaponTypesResponse(tbl) + return resp.make() + + async def handle_d532(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_items + tbl = self.load_data_csv('Item') + resp = SaoGetMItemsResponse(tbl) + return resp.make() + + async def handle_d534(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_item_types + tbl = self.load_data_csv('ItemType') + resp = SaoGetMItemTypesResponse(tbl) + return resp.make() + + async def handle_d536(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_buff_items + tbl = self.load_data_csv('BuffItem') + resp = SaoGetMBuffItemsResponse(tbl) + return resp.make() + + async def handle_d538(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_enemies + tbl = self.load_data_csv('Enemy') + resp = SaoGetMEnemiesResponse(tbl) + return resp.make() + + async def handle_d53a(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_enemy_sets + tbl = self.load_data_csv('EnemySet') + resp = SaoGetMEnemySetsResponse(tbl) + return resp.make() + + async def handle_d53c(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_enemy_kinds + tbl = self.load_data_csv('EnemyKind') + resp = SaoGetMEnemyKindsResponse(tbl) + return resp.make() + + async def handle_d53e(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_enemy_categories + tbl = self.load_data_csv('EnemyCategory') + resp = SaoGetMEnemyCategoriesResponse(tbl) + return resp.make() + + async def handle_d540(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_units + tbl = self.load_data_csv('Unit') + resp = SaoGetMUnitsResponse(tbl) + return resp.make() + + async def handle_d542(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_unit_gimmicks + tbl = self.load_data_csv('UnitGimmick') + resp = SaoGetMUnitGimmicksResponse(tbl) + return resp.make() + + async def handle_d544(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_unit_collisions + tbl = self.load_data_csv('UnitCollision') + resp = SaoGetMUnitCollisionsResponse(tbl) + return resp.make() + + async def handle_d546(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_unit_powers + tbl = self.load_data_csv('UnitPower') + resp = SaoGetMUnitPowersResponse(tbl) + return resp.make() + + async def handle_d548(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_gimmick_attacks + tbl = self.load_data_csv('GimmickAttack') + resp = SaoGetMGimmickAttacksResponse(tbl) + return resp.make() + + async def handle_d54a(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_chara_attacks + tbl = self.load_data_csv('CharaAttack') + resp = SaoGetMCharaAttacksResponse(tbl) + return resp.make() + + async def handle_d54c(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_boss_attacks + tbl = self.load_data_csv('BossAttack') + resp = SaoGetMBossAttacksResponse(tbl) + return resp.make() + + async def handle_d54e(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_monster_attacks + tbl = self.load_data_csv('MonsterAttack') + resp = SaoGetMMonsterAttacksResponse(tbl) + return resp.make() + + async def handle_d550(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_monster_actions + tbl = self.load_data_csv('MonsterAction') + resp = SaoGetMMonsterActionsResponse(tbl) + return resp.make() + + async def handle_d552(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_properties + tbl = self.load_data_csv('Property') + resp = SaoGetMPropertiesResponse(tbl) + return resp.make() + + async def handle_d554(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_property_tables + tbl = self.load_data_csv('PropertyTable') + resp = SaoGetMPropertyTablesResponse(tbl) + return resp.make() + + async def handle_d556(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_property_types + tbl = self.load_data_csv('PropertyType') + resp = SaoGetMPropertyTypesResponse(tbl) + return resp.make() + + async def handle_d558(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_skills + tbl = self.load_data_csv('Skill') + resp = SaoGetMSkillsResponse(tbl) + return resp.make() + + async def handle_d55a(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_skill_tables + tbl = self.load_data_csv('SkillTable') + resp = SaoGetMSkillTablesResponse(tbl) + return resp.make() + + async def handle_d55c(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_skill_levels + tbl = self.load_data_csv('SkillLevel') + resp = SaoGetMSkillLevelsResponse(tbl) + return resp.make() + + async def handle_d55e(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_awakenings + tbl = self.load_data_csv('Awakening') + resp = SaoGetMAwakeningsResponse(tbl) + return resp.make() + + async def handle_d560(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_synchro_skills + tbl = self.load_data_csv('SynchroSkill') + resp = SaoGetMSynchroSkillsResponse(tbl) + return resp.make() + + async def handle_d562(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_sound_skill_cut_in_voices + tbl = self.load_data_csv('Sound_SkillCutInVoice') + resp = SaoGetMSoundSkillCutInVoicesResponse(tbl) + return resp.make() + + async def handle_d564(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_quest_scenes + tbl = self.load_data_csv('QuestScene') + resp = SaoGetMQuestScenesResponse(tbl) + return resp.make() + + async def handle_d566(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_quest_exist_units + tbl = self.load_data_csv('QuestExistUnit') + resp = SaoGetMQuestExistUnitsResponse(tbl) + return resp.make() + + async def handle_d568(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_quest_episode_append_rewards + tbl = self.load_data_csv('QuestEpisodeAppendRewards') + resp = SaoGetMQuestEpisodeAppendRewardsResponse(tbl) + return resp.make() + + async def handle_d56a(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_side_quests + tbl = self.load_data_csv('SideQuest') + resp = SaoGetMSideQuestsResponse(tbl) + return resp.make() + + async def handle_d56c(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_episodes + tbl = self.load_data_csv('Episode') + resp = SaoGetMEpisodesResponse(tbl) + return resp.make() + + async def handle_d56e(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_episode_chapters + tbl = self.load_data_csv('EpisodeChapter') + resp = SaoGetMEpisodeChaptersResponse(tbl) + return resp.make() + + async def handle_d570(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_episode_parts + tbl = self.load_data_csv('EpisodePart') + resp = SaoGetMEpisodePartsResponse(tbl) + return resp.make() + + async def handle_d572(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_trial_towers + tbl = self.load_data_csv('TrialTower') + resp = SaoGetMTrialTowersResponse(tbl) + return resp.make() + + async def handle_d574(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_ex_towers + req = SaoGetMExTowersRequest(header, request) + tbl = self.load_data_csv('ExTowers') + resp = SaoGetMExTowersResponse(tbl) + return resp.make() + + async def handle_d576(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_ex_tower_quests + req = SaoGetMExTowerQuestsRequest(header, request) + tbl = self.load_data_csv('ExTowerQuests') + resp = SaoGetMExTowerQuestsResponse(tbl) + return resp.make() + + async def handle_d578(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_menu_display_enemies + tbl = self.load_data_csv('MenuDisplayEnemy') + resp = SaoGetMMenuDisplayEnemiesResponse(tbl) + return resp.make() + + async def handle_d57a(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_missions + tbl = self.load_data_csv('Mission') + resp = SaoGetMMissionsResponse(tbl) + return resp.make() + + async def handle_d57c(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_mission_tables + tbl = self.load_data_csv('MissionTable') + resp = SaoGetMMissionTablesResponse(tbl) + return resp.make() + + async def handle_d57e(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_mission_difficulties + tbl = self.load_data_csv('MissionDifficulty') + resp = SaoGetMMissionDifficultiesResponse(tbl) + return resp.make() + + async def handle_d580(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_battle_cameras + tbl = self.load_data_csv('BattleCamera') + resp = SaoGetMBattleCamerasResponse(tbl) + return resp.make() + + async def handle_d582(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_chat_main_stories + tbl = self.load_data_csv('ChatMainStory') + resp = SaoGetMChatMainStoriesResponse(tbl) + return resp.make() + + async def handle_d584(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_chat_side_stories + tbl = self.load_data_csv('ChatSideStory') + resp = SaoGetMChatSideStoriesResponse(tbl) + return resp.make() + + async def handle_d586(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_chat_event_stories + req = SaoGetMChatEventStoriesRequest(header, request) + tbl = self.load_data_csv('ChatEventStory') + resp = SaoGetMChatEventStoriesResponse(tbl) + return resp.make() + + async def handle_d588(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_navigator_charas + tbl = self.load_data_csv('NavigatorChara') + resp = SaoGetMNavigatorCharasResponse(tbl) + return resp.make() + + async def handle_d58a(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_navigator_comments + tbl = self.load_data_csv('NavigatorComment') + resp = SaoGetMNavigatorCommentsResponse(tbl) + return resp.make() + + async def handle_d58c(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_ex_bonus_tables + tbl = self.load_data_csv('ExBonusTable') + resp = SaoGetMExBonusTablesResponse(tbl) + return resp.make() + + async def handle_d58e(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_ex_bonus_conditions + tbl = self.load_data_csv('ExBonusCondition') + resp = SaoGetMExBonusConditionsResponse(tbl) + return resp.make() + + async def handle_d590(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_quest_rare_drops + tbl = self.load_data_csv('QuestRareDrop') + resp = SaoGetMQuestRareDropsResponse(tbl) + return resp.make() + + async def handle_d592(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_quest_special_rare_drop_settings + tbl = self.load_data_csv('QuestSpecialRareDropSettings') + resp = SaoGetMQuestSpecialRareDropSettingsResponse(tbl) + return resp.make() + + async def handle_d594(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_quest_special_rare_drops + tbl = self.load_data_csv('QuestSpecialRareDrops') + resp = SaoGetMQuestSpecialRareDropsResponse(tbl) + return resp.make() + + async def handle_d596(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_quest_tutorials + tbl = self.load_data_csv('QuestTutorial') + resp = SaoGetMQuestTutorialsResponse(tbl) + return resp.make() + + async def handle_d598(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_quest_player_trace_tables + tbl = self.load_data_csv('PlayerTraceTable') + resp = SaoGetMQuestPlayerTraceTablesResponse(tbl) + return resp.make() + + async def handle_d59a(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_quest_stills + tbl = self.load_data_csv('QuestStill') + resp = SaoGetMQuestStillsResponse(tbl) + return resp.make() + + async def handle_d59c(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_gashas + tbl = self.load_data_csv('Gasha') + resp = SaoGetMGashasResponse(tbl) + return resp.make() + + async def handle_d59e(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_gasha_headers + tbl = self.load_data_csv('GashaHeader') + resp = SaoGetMGashaHeadersResponse(tbl) + return resp.make() + + async def handle_d5a0(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_gasha_lottery_rarities + tbl = self.load_data_csv('GashaLotteryRarity') + resp = SaoGetMGashaLotteryRaritiesResponse(tbl) + return resp.make() + + async def handle_d5a2(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_gasha_prizes + tbl = self.load_data_csv('GashaPrize') + resp = SaoGetMGashaPrizesResponse(tbl) + return resp.make() + + async def handle_d5a4(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_comeback_events + tbl = self.load_data_csv('ComebackEvent') + resp = SaoGetMComebackEventsResponse(tbl) + return resp.make() + + async def handle_d5a6(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_ad_banners + tbl = self.load_data_csv('AdBanners') + resp = SaoGetMAdBannersResponse(tbl) + return resp.make() + + async def handle_d5a8(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_events + tbl = self.load_data_csv('Event') + resp = SaoGetMEventsResponse(tbl) + return resp.make() + + async def handle_d5aa(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_treasure_hunts + req = SaoGetMTreasureHuntsRequest(header, request) + tbl = self.load_data_csv('TreasureHunt') + resp = SaoGetMTreasureHuntsResponse(tbl) + return resp.make() + + async def handle_d5ac(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_treasure_hunt_whole_tasks + req = SaoGetMTreasureHuntWholeTasksRequest(header, request) + tbl = self.load_data_csv('TreasureHuntWholeTask') + resp = SaoGetMTreasureHuntWholeTasksResponse(tbl) + return resp.make() + + async def handle_d5ae(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_treasure_hunt_individual_tasks + req = SaoGetMTreasureHuntIndividualTasksRequest(header, request) + tbl = self.load_data_csv('TreasureHuntIndividualTask') + resp = SaoGetMTreasureHuntIndividualTasksResponse(tbl) + return resp.make() + + async def handle_d5b0(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_treasure_hunt_special_effects + req = SaoGetMTreasureHuntSpecialEffectsRequest(header, request) + tbl = self.load_data_csv('TreasureHuntSpecialEffect') + resp = SaoGetMTreasureHuntSpecialEffectsResponse(tbl) + return resp.make() + + async def handle_d5b2(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_treasure_hunt_event_point_reward_common_rewards + req = SaoGetMTreasureHuntEventPointRewardCommonRewardsRequest(header, request) + tbl = self.load_data_csv('TreasureHuntEventPointRewardCommonReward') + resp = SaoGetMTreasureHuntEventPointRewardCommonRewardsResponse(tbl) + return resp.make() + + async def handle_d5b4(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_treasure_hunt_event_point_reward_titles + req = SaoGetMTreasureHuntEventPointRewardTitlesRequest(header, request) + tbl = self.load_data_csv('TreasureHuntEventPointRewardTitle') + resp = SaoGetMTreasureHuntEventPointRewardTitlesResponse(tbl) + return resp.make() + + async def handle_d5b6(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_treasure_hunt_task_texts + tbl = self.load_data_csv('TreasureHuntTaskText') + resp = SaoGetMTreasureHuntTaskTextsResponse(tbl) + return resp.make() + + async def handle_d5b8(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_bnid_serial_codes + tbl = self.load_data_csv('BnidSerialCodes') + resp = SaoGetMBnidSerialCodesResponse(tbl) + return resp.make() + + async def handle_d5ba(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_bnid_serial_code_rewards + tbl = self.load_data_csv('BnidSerialCodeRewards') + resp = SaoGetMBnidSerialCodeRewardsResponse(tbl) + return resp.make() + + async def handle_d5bc(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_support_log + tbl = self.load_data_csv('SupportLog') + resp = SaoGetMSupportLogResponse(tbl) + return resp.make() + + async def handle_d5be(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_support_log_types + tbl = self.load_data_csv('SupportLogType') + resp = SaoGetMSupportLogTypesResponse(tbl) + return resp.make() + + async def handle_d5c0(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_episode_appends + tbl = self.load_data_csv('EpisodeAppends') + resp = SaoGetMEpisodeAppendsResponse(tbl) + return resp.make() + + async def handle_d5c2(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_quest_defrag_match_quests + tbl = self.load_data_csv('DefragMatchQuest') + resp = SaoGetMQuestDefragMatchQuestsResponse(tbl) + return resp.make() + + async def handle_d5c4(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_quest_defrag_match_quest_boss_tables + tbl = self.load_data_csv('DefragMatchBossTable') + resp = SaoGetMQuestDefragMatchQuestBossTablesResponse(tbl) + return resp.make() + + async def handle_d5c6(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_defrag_matches + req = SaoGetMDefragMatchesRequest(header, request) + tbl = self.load_data_csv('DefragMatchs') + resp = SaoGetMDefragMatchesResponse(tbl) + return resp.make() + + async def handle_d5c8(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_defrag_match_seed + req = SaoGetMDefragMatchSeedRequest(header, request) + tbl = self.load_data_csv('DefragMatchSeed') + resp = SaoGetMDefragMatchSeedResponse(tbl) + return resp.make() + + async def handle_d5ca(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_defrag_match_special_effects + req = SaoGetMDefragMatchSpecialEffectsRequest(header, request) + tbl = self.load_data_csv('DefragMatchSpecialEffects') + resp = SaoGetMDefragMatchSpecialEffectsResponse(tbl) + return resp.make() + + async def handle_d5cc(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_defrag_match_grades + req = SaoGetMDefragMatchGradesRequest(header, request) + tbl = self.load_data_csv('DefragMatchGrade') + resp = SaoGetMDefragMatchGradesResponse(tbl) + return resp.make() + + async def handle_d5ce(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_defrag_match_cpu_units + tbl = self.load_data_csv('DefragMatchCpuUnits') + resp = SaoGetMDefragMatchCpuUnitsResponse(tbl) + return resp.make() + + async def handle_d5d0(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_defrag_match_cpu_support_logs + tbl = self.load_data_csv('DefragMatchCpuSupportLogs') + resp = SaoGetMDefragMatchCpuSupportLogsResponse(tbl) + return resp.make() + + async def handle_d5d2(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_defrag_match_period_bonuses + req = SaoGetMDefragMatchPeriodBonusesRequest(header, request) + tbl = self.load_data_csv('DefragMatchPeriodBonuses') + resp = SaoGetMDefragMatchPeriodBonusesResponse(tbl) + return resp.make() + + async def handle_d5d4(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_defrag_match_random_bonus_tables + req = SaoGetMDefragMatchRandomBonusTablesRequest(header, request) + tbl = self.load_data_csv('DefragMatchRandomBonusTables') + resp = SaoGetMDefragMatchRandomBonusTablesResponse(tbl) + return resp.make() + + async def handle_d5d6(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_defrag_match_random_bonus_conditions + tbl = self.load_data_csv('DefragMatchRandomBonusConditions') + resp = SaoGetMDefragMatchRandomBonusConditionsResponse(tbl) + return resp.make() + + async def handle_d5d8(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_defrag_match_rare_drops + req = SaoGetMDefragMatchRareDropsRequest(header, request) + tbl = self.load_data_csv('DefragMatchRareDrops') + resp = SaoGetMDefragMatchRareDropsResponse(tbl) + return resp.make() + + async def handle_d5da(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: # master_data/get_m_yui_medal_shops - req = GetMYuiMedalShopDataRequest(header, request) - resp = GetMYuiMedalShopDataResponse(header.cmd + 1) - - shops = self.load_data_csv("YuiMedalShops") - for shop in shops: - tmp = YuiMedalShopData.from_args(int(shop['YuiMedalShopId']), shop['Name'], shop['Description']) - tmp.selling_yui_medal = int(shop['SellingYuiMedal']) - tmp.selling_col = int(shop['SellingCol']) - tmp.selling_event_item_id = int(shop['SellingEventItemId']) - tmp.selling_event_item_num = int(shop['SellingEventItemNum']) - tmp.selling_ticket_num = int(shop['SellingTicketNum']) - tmp.purchase_limit = int(shop['PurchaseLimit']) - tmp.pick_up_flag = 1 if shop['PickUpFlag'] == "True" else 0 - tmp.product_category = int(shop['ProductCategory']) - tmp.sales_type = int(shop['SalesType']) - tmp.target_days = int(shop['TargetDays']) - tmp.target_hour = int(shop['TargetHour']) - tmp.interval_hour = int(shop['IntervalHour']) - tmp.sort = int(shop['Sort']) - - tmp.sales_end_date = datetime(2121, 1, 1, 0, 0, 0, 0) # always open - - resp.data_list.append(tmp) - self.logger.debug(f"Load {len(resp.data_list)} Yui Medal Shops") + tbl = self.load_data_csv('YuiMedalShops') + resp = SaoGetMYuiMedalShopsResponse(tbl) return resp.make() - - async def handle_d5dc(self, header: SaoRequestHeader, request: bytes) -> bytes: + + async def handle_d5dc(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: # master_data/get_m_yui_medal_shop_items - req = GetMYuiMedalShopItemsRequest(header, request) - resp = GetMYuiMedalShopItemsResponse(header.cmd + 1) - - shops = self.load_data_csv("YuiMedalShopItems") - for shop in shops: - tmp = YuiMedalShopItemData.from_args(int(shop['YuiMedalShopItemId']), int(shop['YuiMedalShopId']), int(shop['CommonRewardType']), int(shop['CommonRewardId']), int(shop['CommonRewardNum']), int(shop['Strength'])) - - tmp.property1_property_id = int(shop['Property1PropertyId']) - tmp.property1_value1 = int(shop['Property1Value1']) - tmp.property1_value2 = int(shop['Property1Value2']) - - tmp.property2_property_id = int(shop['Property2PropertyId']) - tmp.property2_value1 = int(shop['Property2Value1']) - tmp.property2_value2 = int(shop['Property2Value2']) - - tmp.property3_property_id = int(shop['Property3PropertyId']) - tmp.property3_value1 = int(shop['Property3Value1']) - tmp.property3_value2 = int(shop['Property3Value2']) - - tmp.property4_property_id = int(shop['Property4PropertyId']) - tmp.property4_value1 = int(shop['Property4Value1']) - tmp.property4_value2 = int(shop['Property4Value2']) - - resp.data_list.append(tmp) - - self.logger.debug(f"Load {len(resp.data_list)} Yui Medal Shop Items") + tbl = self.load_data_csv('YuiMedalShopItems') + resp = SaoGetMYuiMedalShopItemsResponse(tbl) return resp.make() - - async def handle_d5fc(self, header: SaoRequestHeader, request: bytes) -> bytes: + + async def handle_d5de(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_event_scenes + req = SaoGetMEventScenesRequest(header, request) + tbl = self.load_data_csv('EventScenes') + resp = SaoGetMEventScenesResponse(tbl) + return resp.make() + + async def handle_d5e0(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_generic_campaign_periods + tbl = self.load_data_csv('GenericCampaignPeriods') + resp = SaoGetMGenericCampaignPeriodsResponse(tbl) + return resp.make() + + async def handle_d5e2(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_beginner_missions + tbl = self.load_data_csv('BeginnerMissions') + resp = SaoGetMBeginnerMissionsResponse(tbl) + return resp.make() + + async def handle_d5e4(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_beginner_mission_conditions + req = SaoGetMBeginnerMissionConditionsRequest(header, request) + tbl = self.load_data_csv('BeginnerMissionConditions') + resp = SaoGetMBeginnerMissionConditionsResponse(tbl) + return resp.make() + + async def handle_d5e6(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_beginner_mission_rewards + req = SaoGetMBeginnerMissionRewardsRequest(header, request) + tbl = self.load_data_csv('BeginnerMissionRewards') + resp = SaoGetMBeginnerMissionRewardsResponse(tbl) + return resp.make() + + async def handle_d5e8(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_beginner_mission_seat_conditions + req = SaoGetMBeginnerMissionSeatConditionsRequest(header, request) + tbl = self.load_data_csv('BeginnerMissionSeatConditions') + resp = SaoGetMBeginnerMissionSeatConditionsResponse(tbl) + return resp.make() + + async def handle_d5ea(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_beginner_mission_seat_rewards + req = SaoGetMBeginnerMissionSeatRewardsRequest(header, request) + tbl = self.load_data_csv('BeginnerMissionSeatRewards') + resp = SaoGetMBeginnerMissionSeatRewardsResponse(tbl) + return resp.make() + + async def handle_d5ec(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_event_items + tbl = self.load_data_csv('EventItems') + resp = SaoGetMEventItemsResponse(tbl) + return resp.make() + + async def handle_d5ee(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_event_monsters + req = SaoGetMEventMonstersRequest(header, request) + tbl = self.load_data_csv('EventMonsters') + resp = SaoGetMEventMonstersResponse(tbl) + return resp.make() + + async def handle_d5f0(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_yui_medal_bonuses + tbl = self.load_data_csv('YuiMedalBonus') + resp = SaoGetMYuiMedalBonusesResponse(tbl) + return resp.make() + + async def handle_d5f2(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_yui_medal_bonus_conditions + tbl = self.load_data_csv('YuiMedalBonusCondition') + resp = SaoGetMYuiMedalBonusConditionsResponse(tbl) + return resp.make() + + async def handle_d5f4(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_gasha_medals + tbl = self.load_data_csv('GashaMedals') + resp = SaoGetMGashaMedalsResponse(tbl) + return resp.make() + + async def handle_d5f6(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_gasha_medal_types + tbl = self.load_data_csv('GashaMedalTypes') + resp = SaoGetMGashaMedalTypesResponse(tbl) + return resp.make() + + async def handle_d5f8(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_gasha_medal_settings + req = SaoGetMGashaMedalSettingsRequest(header, request) + tbl = self.load_data_csv('GashaMedalSettings') + resp = SaoGetMGashaMedalSettingsResponse(tbl) + return resp.make() + + async def handle_d5fa(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_gasha_medal_bonuses + tbl = self.load_data_csv('GashaMedalBonuses') + resp = SaoGetMGashaMedalBonusesResponse(tbl) + return resp.make() + + async def handle_d5fc(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: # master_data/get_m_gasha_medal_shops - req = GetMGashaMedalShopsRequest(header, request) - resp = GetMGashaMedalShopsResponse(header.cmd + 1) - - shops = self.load_data_csv("GashaMedalShops") - for shop in shops: - tmp = GashaMedalShop.from_args(int(shop['GashaMedalShopId']), shop['Name'], int(shop['GashaMedalId']), int(shop['UseGashaMedalNum']), int(shop['PurchaseLimit'])) - tmp.sales_end_date = datetime(2121, 1, 1, 0, 0, 0, 0) # always open - - resp.data_list.append(tmp) - - self.logger.debug(f"Load {len(resp.data_list)} Gasha Medal Shops") + tbl = self.load_data_csv('GashaMedalShops') + resp = SaoGetMGashaMedalShopsResponse(tbl) return resp.make() - - async def handle_d5fe(self, header: SaoRequestHeader, request: bytes) -> bytes: - # master_data/get_m_gasha_medal_shop_items - return SaoNoopResponse(header.cmd + 1).make() - - async def handle_d604(self, header: SaoRequestHeader, request: bytes) -> bytes: - # master_data_2/get_m_res_earn_campaign_shops - req = GetMResEarnCampaignShopsRequest(header, request) - resp = GetMResEarnCampaignShopsResponse(header.cmd + 1) - - shops = self.load_data_csv("ResEarnCampaignShops") - for shop in shops: - tmp = ResEarnCampaignShop.from_args(int(shop['ResEarnCampaignShopId']), int(shop['ResEarnCampaignApplicationId']), shop['Name']) - tmp.selling_yui_medal = int(shop['SellingYuiMedal']) - tmp.selling_col = int(shop['SellingCol']) - tmp.selling_event_item_id = int(shop['SellingEventItemId']) - tmp.selling_event_item_num = int(shop['SellingEventItemNum']) - tmp.purchase_limit = int(shop['PurchaseLimit']) - tmp.get_application_point = int(shop['GetApplicationPoint']) - - tmp.sales_end_date = datetime(2121, 1, 1, 0, 0, 0, 0) # always open - - resp.data_list.append(tmp) - #self.logger.debug(f"Load {len(resp.data_list)} Res Earn Campaign Shops") - return SaoNoopResponse(header.cmd + 1).make() - - async def handle_d606(self, header: SaoRequestHeader, request: bytes) -> bytes: - # master_data_2/get_m_res_earn_campaign_shop_items - return SaoNoopResponse(header.cmd + 1).make() - - async def handle_c108(self, header: SaoRequestHeader, request: bytes) -> bytes: - # common/logout_ticket_unpurchased - req = SaoLogoutTicketUnpurchasedRequest(header, request) - return SaoLogoutTicketUnpurchasedResponse(header.cmd + 1).make() - - async def handle_cb02(self, header: SaoRequestHeader, request: bytes) -> bytes: - # quest_ranking/get_quest_hierarchy_progress_degrees_ranking_list - req = SaoGetQuestHierarchyProgressDegreesRankingListRequest(header, request) - return SaoGetQuestHierarchyProgressDegreesRankingListResponse(header.cmd + 1).make() - - async def handle_cb04(self, header: SaoRequestHeader, request: bytes) -> bytes: - # quest_ranking/get_quest_popular_hero_log_ranking_list - req = SaoGetQuestPopularHeroLogRankingListRequest(header, request) - return SaoGetQuestPopularHeroLogRankingListResponse(header.cmd + 1).make() + async def handle_d5fe(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data/get_m_gasha_medal_shop_items + req = SaoGetMGashaMedalShopItemsRequest(header, request) + tbl = self.load_data_csv('GashaMedalShopItems') + resp = SaoGetMGashaMedalShopItemsResponse(tbl) + return resp.make() + + async def handle_d600(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data2/get_mres_earn_campaign_applications + tbl = self.load_data_csv('ResEarnCampaignApplications') + resp = SaoGetMResEarnCampaignApplicationsResponse(tbl) + return resp.make() + + async def handle_d602(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data2/get_mres_earn_campaign_application_products + req = SaoGetMResEarnCampaignApplicationProductsRequest(header, request) + tbl = self.load_data_csv('ResEarnCampaignApplicationProducts') + resp = SaoGetMResEarnCampaignApplicationProductsResponse(tbl) + return resp.make() + + async def handle_d604(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data2/get_mres_earn_campaign_shops + tbl = self.load_data_csv('ResEarnCampaignShops') + resp = SaoGetMResEarnCampaignShopsResponse(tbl) + return resp.make() + + async def handle_d606(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data2/get_mres_earn_campaign_shop_items + req = SaoGetMResEarnCampaignShopItemsRequest(header, request) + tbl = self.load_data_csv('ResEarnCampaignShopItems') + resp = SaoGetMResEarnCampaignShopItemsResponse(tbl) + return resp.make() + + async def handle_d608(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data2/get_mpaying_yui_medal_bonuses + tbl = self.load_data_csv('PayingYuiMedalBonuses') + resp = SaoGetMPayingYuiMedalBonusesResponse(tbl) + return resp.make() + + async def handle_d60a(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data2/get_mac_login_bonuses + tbl = self.load_data_csv('AcLoginBonuses') + resp = SaoGetMAcLoginBonusesResponse(tbl) + return resp.make() + + async def handle_d60c(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data2/get_mplay_campaigns + tbl = self.load_data_csv('PlayCampaigns') + resp = SaoGetMPlayCampaignsResponse(tbl) + return resp.make() + + async def handle_d60e(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data2/get_mplay_campaign_rewards + req = SaoGetMPlayCampaignRewardsRequest(header, request) + tbl = self.load_data_csv('PlayCampaignRewards') + resp = SaoGetMPlayCampaignRewardsResponse(tbl) + return resp.make() + + async def handle_d610(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data2/get_mgasha_free_campaigns + tbl = self.load_data_csv('GashaFreeCampaigns') + resp = SaoGetMGashaFreeCampaignsResponse(tbl) + return resp.make() + + async def handle_d612(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data2/get_mquest_drop_boost_campaigns + tbl = self.load_data_csv('QuestDropBoostCampaigns') + resp = SaoGetMQuestDropBoostCampaignsResponse(tbl) + return resp.make() + + async def handle_d614(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data2/get_mfirst_ticket_purchase_campaigns + tbl = self.load_data_csv('FirstTicketPurchaseCampaigns') + resp = SaoGetMFirstTicketPurchaseCampaignsResponse(tbl) + return resp.make() + + async def handle_d616(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data2/get_mlinked_site_reg_campaigns + tbl = self.load_data_csv('LinkedSiteRegCampaigns') + resp = SaoGetMLinkedSiteRegCampaignsResponse(tbl) + return resp.make() + + async def handle_d618(self, header: SaoRequestHeader, request: bytes, src_ip: str) -> bytes: + # master_data2/get_mlinked_site_reg_campaign_rewards + req = SaoGetMLinkedSiteRegCampaignRewardsRequest(header, request) + tbl = self.load_data_csv('LinkedSiteRegCampaignRewards') + resp = SaoGetMLinkedSiteRegCampaignRewardsResponse(tbl) + return resp.make() diff --git a/titles/sao/config.py b/titles/sao/config.py index dfae3c0..892fdca 100644 --- a/titles/sao/config.py +++ b/titles/sao/config.py @@ -30,9 +30,21 @@ class SaoServerConfig: ) @property - def use_https(self) -> bool: + def photon_app_id(self) -> str: return CoreConfig.get_config_field( - self.__config, "sao", "server", "use_https", default=False + self.__config, "sao", "server", "photon_app_id", default="7df3a2f6-d69d-4073-aafe-810ee61e1cea" + ) + + @property + def data_version(self) -> int: + return CoreConfig.get_config_field( + self.__config, "sao", "server", "data_version", default=1 + ) + + @property + def game_version(self) -> int: + return CoreConfig.get_config_field( + self.__config, "sao", "server", "game_version", default=33 ) class SaoCryptConfig: @@ -50,12 +62,6 @@ class SaoCryptConfig: return CoreConfig.get_config_field( self.__config, "sao", "crypt", "key", default="" ) - - @property - def iv(self) -> str: - return CoreConfig.get_config_field( - self.__config, "sao", "crypt", "iv", default="" - ) class SaoHashConfig: def __init__(self, parent_config: "SaoConfig"): @@ -73,9 +79,31 @@ class SaoHashConfig: self.__config, "sao", "hash", "hash_base", default="" ) +class SaoCardConfig: + def __init__(self, parent_config: "SaoConfig"): + self.__config = parent_config + + @property + def enable(self) -> bool: + return CoreConfig.get_config_field( + self.__config, "sao", "card", "enable", default=True + ) + + @property + def crypt_password(self) -> str: + return CoreConfig.get_config_field( + self.__config, "sao", "card", "crypt_password", default="" + ) + + @property + def crypt_salt(self) -> str: + return CoreConfig.get_config_field( + self.__config, "sao", "card", "crypt_salt", default="" + ) class SaoConfig(dict): def __init__(self) -> None: self.server = SaoServerConfig(self) self.crypt = SaoCryptConfig(self) self.hash = SaoHashConfig(self) + self.card = SaoCardConfig(self) diff --git a/titles/sao/const.py b/titles/sao/const.py index d181556..d687c64 100644 --- a/titles/sao/const.py +++ b/titles/sao/const.py @@ -1,5 +1,4 @@ -from enum import Enum - +from enum import IntEnum class SaoConstants: GAME_CODE = "SDEW" @@ -9,8 +8,8 @@ class SaoConstants: VER_SAO = 0 - VERSION_NAMES = ("Sword Art Online Arcade") - + VERSION_NAMES = ("Sword Art Online Arcade",) + SERIAL_IDENT_SATALITE = 4 SERIAL_IDENT_TERMINAL = 5 @@ -23,3 +22,653 @@ class SaoConstants: @classmethod def game_ver_to_string(cls, ver: int): return cls.VERSION_NAMES[ver] + +class RewardType(IntEnum): + None_ = 0 + HeroLog = 1 + Equipment = 2 + Item = 3 + Col = 4 + VP = 5 + YuiMadal = 6 + VPGashaTicket = 7 + SupportLog = 8 + EpisodeAppend = 9 + EventItem = 10 + Ticket = 11 + +class ItemType(IntEnum): + ADD_HERO_PROPERTY = 1 + ADD_WEAPON_PROPERTY = 2 + ADD_ARMOR_PROPERTY = 3 + UNLOCK_PROPERTY = 4 + REMOVE_PROPERTY = 5 + ADD_SKILL_SLOT = 6 + ADD_XP = 7 + REDEMPTION = 8 + HERO_LEVEL_LIMIT_BREAK = 9 + WEAPON_LEVEL_LIMIT_BREAK = 10 + ARMOR_LEVEL_LIMIT_BREAK = 11 + ADD_AWAKENING_XP = 12 + +class ExBonusCondition(IntEnum): + CLEAR_UNDER_X_SECS = 1 + DEFEAT_X_MONSTER_Y_TIMES = 2 + DEFEAT_X_MONSTERS = 3 + CLEAR_X_MISSIONS = 4 + CLEAR_MISSION_DIFFICULTY_X = 5 + COLLECT_X_LOGS = 6 + CLEAR_SKILL_LEVEL_X = 7 + NO_LOSSES = 8 + ACCEL_X_TIMES = 9 + MAX_COMBO_X = 10 + MULTIPLAYER_CLEAR_X = 11 + +class UnanalyzedLogGrade(IntEnum): + WHITE = 1 + COPPER = 2 + SILVER = 3 + GOLD = 4 + RAINBOW = 5 + +class QuestType(IntEnum): + EPISODE = 1 + TRIAL_TOWER = 2 + SIDE = 3 + VERSUS = 4 + EX_TOWER = 5 + EPISODE_CHAPTER = 6 # Unused + +class GameconnectCmd(IntEnum): + TICKET_REQUEST = 0xC000 + TICKET_RESPONSE = 0xC001 + GET_APP_VERSIONS_REQUEST = 0xC100 + GET_APP_VERSIONS_RESPONSE = 0xC101 + MASTER_DATA_VERSION_CHECK_REQUEST = 0xC102 + MASTER_DATA_VERSION_CHECK_RESPONSE = 0xC103 + LOGIN_REQUEST = 0xC104 + LOGIN_RESPONSE = 0xC105 + LOGOUT_REQUEST = 0xC106 + LOGOUT_RESPONSE = 0xC107 + LOGOUT_TICKET_UNPURCHASED_REQUEST = 0xC108 + LOGOUT_TICKET_UNPURCHASED_RESPONSE = 0xC109 + PAYING_PLAY_START_REQUEST = 0xC10A + PAYING_PLAY_START_RESPONSE = 0xC10B + PAYING_PLAY_END_REQUEST = 0xC10C + PAYING_PLAY_END_RESPONSE = 0xC10D + PURCHASE_TICKET_REQUEST = 0xC10E + PURCHASE_TICKET_RESPONSE = 0xC10F + CONSUME_TICKET_REQUEST = 0xC110 + CONSUME_TICKET_RESPONSE = 0xC111 + ADD_CREDIT_REQUEST = 0xC112 + ADD_CREDIT_RESPONSE = 0xC113 + CONSUME_CREDIT_REQUEST = 0xC114 + CONSUME_CREDIT_RESPONSE = 0xC115 + PURCHASE_TICKET_GUEST_REQUEST = 0xC116 + PURCHASE_TICKET_GUEST_RESPONSE = 0xC117 + CONSUME_TICKET_GUEST_REQUEST = 0xC118 + CONSUME_TICKET_GUEST_RESPONSE = 0xC119 + ADD_CREDIT_GUEST_REQUEST = 0xC11A + ADD_CREDIT_GUEST_RESPONSE = 0xC11B + CONSUME_CREDIT_GUEST_REQUEST = 0xC11C + CONSUME_CREDIT_GUEST_RESPONSE = 0xC11D + GET_AUTH_CARD_DATA_REQUEST = 0xC11E + GET_AUTH_CARD_DATA_RESPONSE = 0xC11F + GET_ACCESS_CODE_BY_KEITAI_REQUEST = 0xC120 + GET_ACCESS_CODE_BY_KEITAI_RESPONSE = 0xC121 + GET_MAINTENANCE_INFO_REQUEST = 0xC122 + GET_MAINTENANCE_INFO_RESPONSE = 0xC123 + GET_RESOURCE_PATH_INFO_REQUEST = 0xC124 + GET_RESOURCE_PATH_INFO_RESPONSE = 0xC125 + VALIDATION_ERROR_NOTIFICATION_REQUEST = 0xC126 + VALIDATION_ERROR_NOTIFICATION_RESPONSE = 0xC127 + POWER_CUTTING_RETURN_NOTIFICATION_REQUEST = 0xC128 + POWER_CUTTING_RETURN_NOTIFICATION_RESPONSE = 0xC129 + GIVE_FREE_TICKET_REQUEST = 0xC12A + GIVE_FREE_TICKET_RESPONSE = 0xC12B + MATCHING_ERROR_NOTIFICATION_REQUEST = 0xC12C + MATCHING_ERROR_NOTIFICATION_RESPONSE = 0xC12D + AC_CABINET_BOOT_NOTIFICATION_REQUEST = 0xC12E + AC_CABINET_BOOT_NOTIFICATION_RESPONSE = 0xC12F + FIRST_TUTORIAL_END_REQUEST = 0xC200 + FIRST_TUTORIAL_END_RESPONSE = 0xC201 + VARIOUS_TUTORIAL_END_REQUEST = 0xC202 + VARIOUS_TUTORIAL_END_RESPONSE = 0xC203 + GET_VARIOUS_TUTORIAL_DATA_LIST_REQUEST = 0xC204 + GET_VARIOUS_TUTORIAL_DATA_LIST_RESPONSE = 0xC205 + DISCHARGE_PROFILE_CARD_REQUEST = 0xC300 + DISCHARGE_PROFILE_CARD_RESPONSE = 0xC301 + DISCHARGE_RESOURCE_CARD_REQUEST = 0xC302 + DISCHARGE_RESOURCE_CARD_RESPONSE = 0xC303 + DISCHARGE_RESOURCE_CARD_COMPLETE_REQUEST = 0xC304 + DISCHARGE_RESOURCE_CARD_COMPLETE_RESPONSE = 0xC305 + SCAN_QR_QUEST_PROFILE_CARD_REQUEST = 0xC306 + SCAN_QR_QUEST_PROFILE_CARD_RESPONSE = 0xC307 + SCAN_QR_SHOP_RESOURCE_CARD_REQUEST = 0xC308 + SCAN_QR_SHOP_RESOURCE_CARD_RESPONSE = 0xC309 + SCAN_QR_QUEST_RESOURCE_CARD_REQUEST = 0xC30A + SCAN_QR_QUEST_RESOURCE_CARD_RESPONSE = 0xC30B + CHECK_YUI_MEDAL_GET_CONDITION_REQUEST = 0xC400 + CHECK_YUI_MEDAL_GET_CONDITION_RESPONSE = 0xC401 + GET_YUI_MEDAL_BONUS_USER_DATA_REQUEST = 0xC402 + GET_YUI_MEDAL_BONUS_USER_DATA_RESPONSE = 0xC403 + CHECK_COMEBACK_EVENT_REQUEST = 0xC404 + CHECK_COMEBACK_EVENT_RESPONSE = 0xC405 + CHANGE_MY_STORE_REQUEST = 0xC406 + CHANGE_MY_STORE_RESPONSE = 0xC407 + CHECK_TITLE_GET_DECISION_REQUEST = 0xC408 + CHECK_TITLE_GET_DECISION_RESPONSE = 0xC409 + CHECK_PROFILE_CARD_USED_REWARD_REQUEST = 0xC40A + CHECK_PROFILE_CARD_USED_REWARD_RESPONSE = 0xC40B + CHECK_AC_LOGIN_BONUS_REQUEST = 0xC40C + CHECK_AC_LOGIN_BONUS_RESPONSE = 0xC40D + GET_USER_BASIC_DATA_REQUEST = 0xC500 + GET_USER_BASIC_DATA_RESPONSE = 0xC501 + GET_VP_GASHA_TICKET_DATA_LIST_REQUEST = 0xC502 + GET_VP_GASHA_TICKET_DATA_LIST_RESPONSE = 0xC503 + GET_PRESENT_BOX_NUM_REQUEST = 0xC504 + GET_PRESENT_BOX_NUM_RESPONSE = 0xC505 + GET_HERO_LOG_USER_DATA_LIST_REQUEST = 0xC600 + GET_HERO_LOG_USER_DATA_LIST_RESPONSE = 0xC601 + GET_EQUIPMENT_USER_DATA_LIST_REQUEST = 0xC602 + GET_EQUIPMENT_USER_DATA_LIST_RESPONSE = 0xC603 + GET_ITEM_USER_DATA_LIST_REQUEST = 0xC604 + GET_ITEM_USER_DATA_LIST_RESPONSE = 0xC605 + GET_SUPPORT_LOG_USER_DATA_LIST_REQUEST = 0xC606 + GET_SUPPORT_LOG_USER_DATA_LIST_RESPONSE = 0xC607 + GET_EPISODE_APPEND_DATA_LIST_REQUEST = 0xC608 + GET_EPISODE_APPEND_DATA_LIST_RESPONSE = 0xC609 + GET_EVENT_ITEM_DATA_LIST_REQUEST = 0xC60A + GET_EVENT_ITEM_DATA_LIST_RESPONSE = 0xC60B + GET_GASHA_MEDAL_USER_DATA_LIST_REQUEST = 0xC60C + GET_GASHA_MEDAL_USER_DATA_LIST_RESPONSE = 0xC60D + GET_SHOP_RESOURCE_SALES_DATA_LIST_REQUEST = 0xC700 + GET_SHOP_RESOURCE_SALES_DATA_LIST_RESPONSE = 0xC701 + PURCHASE_SHOP_RESOURCE_REQUEST = 0xC702 + PURCHASE_SHOP_RESOURCE_RESPONSE = 0xC703 + DISCARD_SHOP_RESOURCE_REQUEST = 0xC704 + DISCARD_SHOP_RESOURCE_RESPONSE = 0xC705 + GET_TITLE_USER_DATA_LIST_REQUEST = 0xC800 + GET_TITLE_USER_DATA_LIST_RESPONSE = 0xC801 + CHANGE_TITLE_REQUEST = 0xC802 + CHANGE_TITLE_RESPONSE = 0xC803 + GET_PARTY_DATA_LIST_REQUEST = 0xC804 + GET_PARTY_DATA_LIST_RESPONSE = 0xC805 + CHANGE_PARTY_REQUEST = 0xC806 + CHANGE_PARTY_RESPONSE = 0xC807 + GET_SUPPORT_LOG_PARTY_DATA_LIST_REQUEST = 0xC808 + GET_SUPPORT_LOG_PARTY_DATA_LIST_RESPONSE = 0xC809 + CHANGE_SUPPORT_LOG_PARTY_REQUEST = 0xC80A + CHANGE_SUPPORT_LOG_PARTY_RESPONSE = 0xC80B + CHANGE_HERO_LOG_LAST_SETTING_SKILL_REQUEST = 0xC80C + CHANGE_HERO_LOG_LAST_SETTING_SKILL_RESPONSE = 0xC80D + LOCK_RESOURCE_REQUEST = 0xC80E + LOCK_RESOURCE_RESPONSE = 0xC80F + UNLOCK_RESOURCE_REQUEST = 0xC810 + UNLOCK_RESOURCE_RESPONSE = 0xC811 + DISPOSAL_RESOURCE_REQUEST = 0xC812 + DISPOSAL_RESOURCE_RESPONSE = 0xC813 + SYNTHESIZE_ENHANCEMENT_HERO_LOG_REQUEST = 0xC814 + SYNTHESIZE_ENHANCEMENT_HERO_LOG_RESPONSE = 0xC815 + SYNTHESIZE_ENHANCEMENT_EQUIPMENT_REQUEST = 0xC816 + SYNTHESIZE_ENHANCEMENT_EQUIPMENT_RESPONSE = 0xC817 + SYNTHESIZE_ENHANCEMENT_SUPPORT_LOG_REQUEST = 0xC818 + SYNTHESIZE_ENHANCEMENT_SUPPORT_LOG_RESPONSE = 0xC819 + SYNTHESIZE_ABILITY_HERO_LOG_REQUEST = 0xC81A + SYNTHESIZE_ABILITY_HERO_LOG_RESPONSE = 0xC81B + SYNTHESIZE_ABILITY_EQUIPMENT_REQUEST = 0xC81C + SYNTHESIZE_ABILITY_EQUIPMENT_RESPONSE = 0xC81D + SYNTHESIZE_ABILITY_SUPPORT_LOG_REQUEST = 0xC81E + SYNTHESIZE_ABILITY_SUPPORT_LOG_RESPONSE = 0xC820 + GET_QUEST_SCENE_USER_DATA_LIST_REQUEST = 0xC900 + GET_QUEST_SCENE_USER_DATA_LIST_RESPONSE = 0xC901 + GET_QUEST_SCENE_PREV_SCAN_PROFILE_CARD_REQUEST = 0xC902 + GET_QUEST_SCENE_PREV_SCAN_PROFILE_CARD_RESPONSE = 0xC903 + EPISODE_PLAY_START_REQUEST = 0xC904 + EPISODE_PLAY_START_RESPONSE = 0xC905 + EPISODE_PLAY_CONTINUE_REQUEST = 0xC906 + EPISODE_PLAY_CONTINUE_RESPONSE = 0xC907 + EPISODE_PLAY_END_REQUEST = 0xC908 + EPISODE_PLAY_END_RESPONSE = 0xC909 + EPISODE_PLAY_END_UNANALYZED_LOG_FIXED_REQUEST = 0xC90A + EPISODE_PLAY_END_UNANALYZED_LOG_FIXED_RESPONSE = 0xC90B + SIDE_QUEST_PLAY_START_REQUEST = 0xC90C + SIDE_QUEST_PLAY_START_RESPONSE = 0xC90D + SIDE_QUEST_PLAY_CONTINUE_REQUEST = 0xC90E + SIDE_QUEST_PLAY_CONTINUE_RESPONSE = 0xC90F + SIDE_QUEST_PLAY_END_REQUEST = 0xC910 + SIDE_QUEST_PLAY_END_RESPONSE = 0xC911 + SIDE_QUEST_PLAY_END_UNANALYZED_LOG_FIXED_REQUEST = 0xC912 + SIDE_QUEST_PLAY_END_UNANALYZED_LOG_FIXED_RESPONSE = 0xC913 + TRIAL_TOWER_PLAY_START_REQUEST = 0xC914 + TRIAL_TOWER_PLAY_START_RESPONSE = 0xC915 + TRIAL_TOWER_PLAY_CONTINUE_REQUEST = 0xC916 + TRIAL_TOWER_PLAY_CONTINUE_RESPONSE = 0xC917 + TRIAL_TOWER_PLAY_END_REQUEST = 0xC918 + TRIAL_TOWER_PLAY_END_RESPONSE = 0xC919 + TRIAL_TOWER_PLAY_END_UNANALYZED_LOG_FIXED_REQUEST = 0xC91A + TRIAL_TOWER_PLAY_END_UNANALYZED_LOG_FIXED_RESPONSE = 0xC91B + DEFRAG_MATCH_QUEST_PLAY_START_REQUEST = 0xC91C + DEFRAG_MATCH_QUEST_PLAY_START_RESPONSE = 0xC91D + DEFRAG_MATCH_QUEST_PLAY_END_REQUEST = 0xC91E + DEFRAG_MATCH_QUEST_PLAY_END_RESPONSE = 0xC91F + DEFRAG_MATCH_QUEST_PLAY_END_UNANALYZED_LOG_FIXED_REQUEST = 0xC920 + DEFRAG_MATCH_QUEST_PLAY_END_UNANALYZED_LOG_FIXED_RESPONSE = 0xC921 + EX_TOWER_QUEST_PLAY_START_REQUEST = 0xC922 + EX_TOWER_QUEST_PLAY_START_RESPONSE = 0xC923 + EX_TOWER_QUEST_PLAY_CONTINUE_REQUEST = 0xC924 + EX_TOWER_QUEST_PLAY_CONTINUE_RESPONSE = 0xC925 + EX_TOWER_QUEST_PLAY_END_REQUEST = 0xC926 + EX_TOWER_QUEST_PLAY_END_RESPONSE = 0xC927 + EX_TOWER_QUEST_PLAY_END_UNANALYZED_LOG_FIXED_REQUEST = 0xC928 + EX_TOWER_QUEST_PLAY_END_UNANALYZED_LOG_FIXED_RESPONSE = 0xC929 + GET_EX_TOWER_USER_DATA_REQUEST = 0xC92A + GET_EX_TOWER_USER_DATA_RESPONSE = 0xC92B + REG_EX_TOWER_HALL_OF_FAME_CONFIRM_FLAG_REQUEST = 0xC92C + REG_EX_TOWER_HALL_OF_FAME_CONFIRM_FLAG_RESPONSE = 0xC92D + REG_EX_TOWER_CONFIRM_FLAG_REQUEST = 0xC92E + REG_EX_TOWER_CONFIRM_FLAG_RESPONSE = 0xC92F + GET_CHAT_SIDE_STORY_USER_DATA_LIST_REQUEST = 0xC930 + GET_CHAT_SIDE_STORY_USER_DATA_LIST_RESPONSE = 0xC931 + GET_USER_QUEST_EPISODE_STATUS_DATA_LIST_REQUEST = 0xC932 + GET_USER_QUEST_EPISODE_STATUS_DATA_LIST_RESPONSE = 0xC933 + APPLY_QUEST_DROP_BOOST_REQUEST = 0xC934 + APPLY_QUEST_DROP_BOOST_RESPONSE = 0xC935 + CREATE_QUEST_SCENE_MULTI_PLAY_ROOM_ID_REQUEST = 0xCA00 + CREATE_QUEST_SCENE_MULTI_PLAY_ROOM_ID_RESPONSE = 0xCA01 + GET_QUEST_SCENE_MULTI_PLAY_PHOTON_SERVER_REQUEST = 0xCA02 + GET_QUEST_SCENE_MULTI_PLAY_PHOTON_SERVER_RESPONSE = 0xCA03 + GET_QUEST_SCENE_MULTI_PLAY_PHOTON_SERVER_BY_ROOM_REQUEST = 0xCA04 + GET_QUEST_SCENE_MULTI_PLAY_PHOTON_SERVER_BY_ROOM_RESPONSE = 0xCA05 + GET_QUEST_BEST_SOCRE_RANKING_LIST_REQUEST = 0xCB00 + GET_QUEST_BEST_SOCRE_RANKING_LIST_RESPONSE = 0xCB01 + GET_QUEST_HIERARCHY_PROGRESS_DEGREES_RANKING_LIST_REQUEST = 0xCB02 + GET_QUEST_HIERARCHY_PROGRESS_DEGREES_RANKING_LIST_RESPONSE = 0xCB03 + GET_QUEST_POPULAR_HERO_LOG_RANKING_LIST_REQUEST = 0xCB04 + GET_QUEST_POPULAR_HERO_LOG_RANKING_LIST_RESPONSE = 0xCB05 + GET_QUEST_EX_TOWER_HIERARCHY_PROGRESS_DEGREES_STORE_RANKING_LIST_REQUEST = 0xCB06 + GET_QUEST_EX_TOWER_HIERARCHY_PROGRESS_DEGREES_STORE_RANKING_LIST_RESPONSE = 0xCB07 + GET_QUEST_EX_TOWER_HIERARCHY_PROGRESS_DEGREES_NATIONAL_RANKING_LIST_REQUEST = 0xCB08 + GET_QUEST_EX_TOWER_HIERARCHY_PROGRESS_DEGREES_NATIONAL_RANKING_LIST_RESPONSE = 0xCB09 + GET_TREASURE_HUNT_BASIC_DATA_REQUEST = 0xCC00 + GET_TREASURE_HUNT_BASIC_DATA_RESPONSE = 0xCC01 + GET_TREASURE_HUNT_WHOLE_TASK_DATA_LIST_REQUEST = 0xCC02 + GET_TREASURE_HUNT_WHOLE_TASK_DATA_LIST_RESPONSE = 0xCC03 + GET_TREASURE_HUNT_INDIVIDUAL_TASK_DATA_LIST_REQUEST = 0xCC04 + GET_TREASURE_HUNT_INDIVIDUAL_TASK_DATA_LIST_RESPONSE = 0xCC05 + TREASURE_HUNT_AD_CONFIRM_NOTIFICATION_REQUEST = 0xCC06 + TREASURE_HUNT_AD_CONFIRM_NOTIFICATION_RESPONSE = 0xCC07 + GET_TREASURE_HUNT_EVENT_POINT_RANKING_USER_DATA_REQUEST = 0xCC08 + GET_TREASURE_HUNT_EVENT_POINT_RANKING_USER_DATA_RESPONSE = 0xCC09 + GET_TREASURE_HUNT_EVENT_POINT_STORE_RANKING_LIST_REQUEST = 0xCC0A + GET_TREASURE_HUNT_EVENT_POINT_STORE_RANKING_LIST_RESPONSE = 0xCC0B + GET_TREASURE_HUNT_EVENT_POINT_NATIONAL_RANKING_LIST_REQUEST = 0xCC0C + GET_TREASURE_HUNT_EVENT_POINT_NATIONAL_RANKING_LIST_RESPONSE = 0xCC0D + GET_DEFRAG_MATCH_BASIC_DATA_REQUEST = 0xCD00 + GET_DEFRAG_MATCH_BASIC_DATA_RESPONSE = 0xCD01 + GET_DEFRAG_MATCH_RANKING_USER_DATA_REQUEST = 0xCD02 + GET_DEFRAG_MATCH_RANKING_USER_DATA_RESPONSE = 0xCD03 + GET_DEFRAG_MATCH_LEAGUE_POINT_RANKING_LIST_REQUEST = 0xCD04 + GET_DEFRAG_MATCH_LEAGUE_POINT_RANKING_LIST_RESPONSE = 0xCD05 + GET_DEFRAG_MATCH_LEAGUE_SCORE_RANKING_LIST_REQUEST = 0xCD06 + GET_DEFRAG_MATCH_LEAGUE_SCORE_RANKING_LIST_RESPONSE = 0xCD07 + DEFRAG_MATCH_AD_CONFIRM_NOTIFICATION_REQUEST = 0xCD08 + DEFRAG_MATCH_AD_CONFIRM_NOTIFICATION_RESPONSE = 0xCD09 + CHECK_GET_SEED_DEFRAG_MATCH_REQUEST = 0xCD0A + CHECK_GET_SEED_DEFRAG_MATCH_RESPONSE = 0xCD0B + REG_DEFRAG_MATCH_HALL_OF_FAME_CONFIRM_FLAG_REQUEST = 0xCD0C + REG_DEFRAG_MATCH_HALL_OF_FAME_CONFIRM_FLAG_RESPONSE = 0xCD0D + GET_EVENT_SCENE_USER_DATA_LIST_REQUEST = 0xCE00 + GET_EVENT_SCENE_USER_DATA_LIST_RESPONSE = 0xCE01 + REG_PLAYED_EVENT_SCENE_USER_DATA_LIST_REQUEST = 0xCE02 + REG_PLAYED_EVENT_SCENE_USER_DATA_LIST_RESPONSE = 0xCE03 + GET_GASHA_LIST_REQUEST = 0xCF00 + GET_GASHA_LIST_RESPONSE = 0xCF01 + EXEC_GASHA_REQUEST = 0xCF02 + EXEC_GASHA_RESPONSE = 0xCF03 + EXEC_CREDIT_GASHA_START_REQUEST = 0xCF04 + EXEC_CREDIT_GASHA_START_RESPONSE = 0xCF05 + EXEC_CREDIT_GASHA_EXTRA_PAYING_REQUEST = 0xCF06 + EXEC_CREDIT_GASHA_EXTRA_PAYING_RESPONSE = 0xCF07 + EXEC_CREDIT_GASHA_END_REQUEST = 0xCF08 + EXEC_CREDIT_GASHA_END_RESPONSE = 0xCF09 + EXEC_CREDIT_GASHA_CARD_DISCHARGE_STATE_MIGRATION_REQUEST = 0xCF0A + EXEC_CREDIT_GASHA_CARD_DISCHARGE_STATE_MIGRATION_RESPONSE = 0xCF0B + EXEC_CREDIT_GASHA_CARD_DISCHARGE_END_REQUEST = 0xCF0C + EXEC_CREDIT_GASHA_CARD_DISCHARGE_END_RESPONSE = 0xCF0D + GET_GASHA_MEDAL_SHOP_USER_DATA_LIST_REQUEST = 0xCF0E + GET_GASHA_MEDAL_SHOP_USER_DATA_LIST_RESPONSE = 0xCF0F + PURCHASE_GASHA_MEDAL_SHOP_ITEM_REQUEST = 0xCF10 + PURCHASE_GASHA_MEDAL_SHOP_ITEM_RESPONSE = 0xCF11 + GET_GASHA_FREE_CAMPAIGN_USER_DATA_REQUEST = 0xCF12 + GET_GASHA_FREE_CAMPAIGN_USER_DATA_RESPONSE = 0xCF13 + GET_ADVENTURE_EXEC_USER_DATA_REQUEST = 0xD000 + GET_ADVENTURE_EXEC_USER_DATA_RESPONSE = 0xD001 + GET_ADVENTURE_PARTY_DATA_LIST_REQUEST = 0xD002 + GET_ADVENTURE_PARTY_DATA_LIST_RESPONSE = 0xD003 + GET_YUI_MEDAL_SHOP_USER_DATA_LIST_REQUEST = 0xD100 + GET_YUI_MEDAL_SHOP_USER_DATA_LIST_RESPONSE = 0xD101 + PURCHASE_YUI_MEDAL_SHOP_ITEM_REQUEST = 0xD102 + PURCHASE_YUI_MEDAL_SHOP_ITEM_RESPONSE = 0xD103 + PURCHASE_EVENT_SHOP_ITEM_REQUEST = 0xD104 + PURCHASE_EVENT_SHOP_ITEM_RESPONSE = 0xD105 + GET_BEGINNER_MISSION_USER_DATA_REQUEST = 0xD200 + GET_BEGINNER_MISSION_USER_DATA_RESPONSE = 0xD201 + GET_BEGINNER_MISSION_PROGRESSES_USER_DATA_LIST_REQUEST = 0xD202 + GET_BEGINNER_MISSION_PROGRESSES_USER_DATA_LIST_RESPONSE = 0xD203 + GET_BEGINNER_MISSION_SEAT_PROGRESSES_USER_DATA_LIST_REQUEST = 0xD204 + GET_BEGINNER_MISSION_SEAT_PROGRESSES_USER_DATA_LIST_RESPONSE = 0xD205 + BEGINNER_MISSION_AD_CONFIRM_NOTIFICATION_REQUEST = 0xD206 + BEGINNER_MISSION_AD_CONFIRM_NOTIFICATION_RESPONSE = 0xD207 + RECEIVE_BEGINNER_MISSION_REWARD_REQUEST = 0xD208 + RECEIVE_BEGINNER_MISSION_REWARD_RESPONSE = 0xD209 + GET_RES_EARN_CAMPAIGN_SHOP_USER_DATA_LIST_REQUEST = 0xD300 + GET_RES_EARN_CAMPAIGN_SHOP_USER_DATA_LIST_RESPONSE = 0xD301 + PURCHASE_RES_EARN_CAMPAIGN_SHOP_ITEM_REQUEST = 0xD302 + PURCHASE_RES_EARN_CAMPAIGN_SHOP_ITEM_RESPONSE = 0xD303 + PAYING_YUI_MEDAL_BONUS_GET_CHECK_REQUEST = 0xD304 + PAYING_YUI_MEDAL_BONUS_GET_CHECK_RESPONSE = 0xD305 + PAYING_YUI_MEDAL_BONUS_AD_CONFIRM_NOTIFICATION_REQUEST = 0xD306 + PAYING_YUI_MEDAL_BONUS_AD_CONFIRM_NOTIFICATION_RESPONSE = 0xD307 + GET_PLAY_CAMPAIGN_USER_DATA_LIST_REQUEST = 0xD308 + GET_PLAY_CAMPAIGN_USER_DATA_LIST_RESPONSE = 0xD309 + GET_PLAY_CAMPAIGN_STORE_USER_DATA_LIST_REQUEST = 0xD30A + GET_PLAY_CAMPAIGN_STORE_USER_DATA_LIST_RESPONSE = 0xD30B + GET_PLAY_CAMPAIGN_REWARD_USER_DATA_LIST_REQUEST = 0xD30C + GET_PLAY_CAMPAIGN_REWARD_USER_DATA_LIST_RESPONSE = 0xD30D + APPLY_FIRST_TICKET_PURCHASE_CAMPAIGN_REQUEST = 0xD30E + APPLY_FIRST_TICKET_PURCHASE_CAMPAIGN_RESPONSE = 0xD30F + GET_FIRST_TICKET_PURCHASE_CAMPAIGN_USER_DATA_REQUEST = 0xD310 + GET_FIRST_TICKET_PURCHASE_CAMPAIGN_USER_DATA_RESPONSE = 0xD311 + GET_LINKED_SITE_REG_CAMPAIGN_USER_DATA_REQUEST = 0xD312 + GET_LINKED_SITE_REG_CAMPAIGN_USER_DATA_RESPONSE = 0xD313 + GET_HERO_LOG_UNIT_USER_DATA_LIST_REQUEST = 0xD400 + GET_HERO_LOG_UNIT_USER_DATA_LIST_RESPONSE = 0xD401 + GET_CHARA_UNIT_USER_DATA_LIST_REQUEST = 0xD402 + GET_CHARA_UNIT_USER_DATA_LIST_RESPONSE = 0xD403 + BNID_SERIAL_CODE_CHECK_REQUEST = 0xD404 + BNID_SERIAL_CODE_CHECK_RESPONSE = 0xD405 + BNID_SERIAL_CODE_ENTRY_BY_APPENDIX_CARD_REQUEST = 0xD406 + BNID_SERIAL_CODE_ENTRY_BY_APPENDIX_CARD_RESPONSE = 0xD407 + GET_M_PLAYER_RANKS_REQUEST = 0xD500 + GET_M_PLAYER_RANKS_RESPONSE = 0xD501 + GET_M_TITLES_REQUEST = 0xD502 + GET_M_TITLES_RESPONSE = 0xD503 + GET_M_FRAGMENTS_REQUEST = 0xD504 + GET_M_FRAGMENTS_RESPONSE = 0xD505 + GET_M_REWARD_TABLES_REQUEST = 0xD506 + GET_M_REWARD_TABLES_RESPONSE = 0xD507 + GET_M_REWARD_SETS_REQUEST = 0xD508 + GET_M_REWARD_SETS_RESPONSE = 0xD509 + GET_M_UNANALYZED_LOG_GRADES_REQUEST = 0xD50A + GET_M_UNANALYZED_LOG_GRADES_RESPONSE = 0xD50B + GET_M_APPOINT_LEADER_PARAMS_REQUEST = 0xD50C + GET_M_APPOINT_LEADER_PARAMS_RESPONSE = 0xD50D + GET_M_APPOINT_LEADER_EFFECTS_REQUEST = 0xD50E + GET_M_APPOINT_LEADER_EFFECTS_RESPONSE = 0xD50F + GET_M_APPOINT_LEADER_EFFECT_TYPES_REQUEST = 0xD510 + GET_M_APPOINT_LEADER_EFFECT_TYPES_RESPONSE = 0xD511 + GET_M_RARITIES_REQUEST = 0xD512 + GET_M_RARITIES_RESPONSE = 0xD513 + GET_M_COMPOSITION_EVENTS_REQUEST = 0xD514 + GET_M_COMPOSITION_EVENTS_RESPONSE = 0xD515 + GET_M_COMPOSITION_PARAMS_REQUEST = 0xD516 + GET_M_COMPOSITION_PARAMS_RESPONSE = 0xD517 + GET_M_GAME_PLAY_PRICES_REQUEST = 0xD518 + GET_M_GAME_PLAY_PRICES_RESPONSE = 0xD519 + GET_M_BUY_TICKETS_REQUEST = 0xD51A + GET_M_BUY_TICKETS_RESPONSE = 0xD51B + GET_M_TIPS_REQUEST = 0xD51C + GET_M_TIPS_RESPONSE = 0xD51D + GET_M_CAPS_REQUEST = 0xD51E + GET_M_CAPS_RESPONSE = 0xD51F + GET_M_HERO_LOG_REQUEST = 0xD520 + GET_M_HERO_LOG_RESPONSE = 0xD521 + GET_M_HERO_LOG_LEVELS_REQUEST = 0xD522 + GET_M_HERO_LOG_LEVELS_RESPONSE = 0xD523 + GET_M_HERO_LOG_ROLES_REQUEST = 0xD524 + GET_M_HERO_LOG_ROLES_RESPONSE = 0xD525 + GET_M_HERO_LOG_TRUST_RANKS_REQUEST = 0xD526 + GET_M_HERO_LOG_TRUST_RANKS_RESPONSE = 0xD527 + GET_M_CHARAS_REQUEST = 0xD528 + GET_M_CHARAS_RESPONSE = 0xD529 + GET_M_CHARA_FRIENDLY_RANKS_REQUEST = 0xD52A + GET_M_CHARA_FRIENDLY_RANKS_RESPONSE = 0xD52B + GET_M_EQUIPMENTS_REQUEST = 0xD52C + GET_M_EQUIPMENTS_RESPONSE = 0xD52D + GET_M_EQUIPMENT_LEVELS_REQUEST = 0xD52E + GET_M_EQUIPMENT_LEVELS_RESPONSE = 0xD52F + GET_M_WEAPON_TYPES_REQUEST = 0xD530 + GET_M_WEAPON_TYPES_RESPONSE = 0xD531 + GET_M_ITEMS_REQUEST = 0xD532 + GET_M_ITEMS_RESPONSE = 0xD533 + GET_M_ITEM_TYPES_REQUEST = 0xD534 + GET_M_ITEM_TYPES_RESPONSE = 0xD535 + GET_M_BUFF_ITEMS_REQUEST = 0xD536 + GET_M_BUFF_ITEMS_RESPONSE = 0xD537 + GET_M_ENEMIES_REQUEST = 0xD538 + GET_M_ENEMIES_RESPONSE = 0xD539 + GET_M_ENEMY_SETS_REQUEST = 0xD53A + GET_M_ENEMY_SETS_RESPONSE = 0xD53B + GET_M_ENEMY_KINDS_REQUEST = 0xD53C + GET_M_ENEMY_KINDS_RESPONSE = 0xD53D + GET_M_ENEMY_CATEGORIES_REQUEST = 0xD53E + GET_M_ENEMY_CATEGORIES_RESPONSE = 0xD53F + GET_M_UNITS_REQUEST = 0xD540 + GET_M_UNITS_RESPONSE = 0xD541 + GET_M_UNIT_GIMMICKS_REQUEST = 0xD542 + GET_M_UNIT_GIMMICKS_RESPONSE = 0xD543 + GET_M_UNIT_COLLISIONS_REQUEST = 0xD544 + GET_M_UNIT_COLLISIONS_RESPONSE = 0xD545 + GET_M_UNIT_POWERS_REQUEST = 0xD546 + GET_M_UNIT_POWERS_RESPONSE = 0xD547 + GET_M_GIMMICK_ATTACKS_REQUEST = 0xD548 + GET_M_GIMMICK_ATTACKS_RESPONSE = 0xD549 + GET_M_CHARA_ATTACKS_REQUEST = 0xD54A + GET_M_CHARA_ATTACKS_RESPONSE = 0xD54B + GET_M_BOSS_ATTACKS_REQUEST = 0xD54C + GET_M_BOSS_ATTACKS_RESPONSE = 0xD54D + GET_M_MONSTER_ATTACKS_REQUEST = 0xD54E + GET_M_MONSTER_ATTACKS_RESPONSE = 0xD54F + GET_M_MONSTER_ACTIONS_REQUEST = 0xD550 + GET_M_MONSTER_ACTIONS_RESPONSE = 0xD551 + GET_M_PROPERTIES_REQUEST = 0xD552 + GET_M_PROPERTIES_RESPONSE = 0xD553 + GET_M_PROPERTY_TABLES_REQUEST = 0xD554 + GET_M_PROPERTY_TABLES_RESPONSE = 0xD555 + GET_M_PROPERTY_TYPES_REQUEST = 0xD556 + GET_M_PROPERTY_TYPES_RESPONSE = 0xD557 + GET_M_SKILLS_REQUEST = 0xD558 + GET_M_SKILLS_RESPONSE = 0xD559 + GET_M_SKILL_TABLES_REQUEST = 0xD55A + GET_M_SKILL_TABLES_RESPONSE = 0xD55B + GET_M_SKILL_LEVELS_REQUEST = 0xD55C + GET_M_SKILL_LEVELS_RESPONSE = 0xD55D + GET_M_AWAKENINGS_REQUEST = 0xD55E + GET_M_AWAKENINGS_RESPONSE = 0xD55F + GET_M_SYNCHRO_SKILLS_REQUEST = 0xD560 + GET_M_SYNCHRO_SKILLS_RESPONSE = 0xD561 + GET_M_SOUND_SKILL_CUT_IN_VOICES_REQUEST = 0xD562 + GET_M_SOUND_SKILL_CUT_IN_VOICES_RESPONSE = 0xD563 + GET_M_QUEST_SCENES_REQUEST = 0xD564 + GET_M_QUEST_SCENES_RESPONSE = 0xD565 + GET_M_QUEST_EXIST_UNITS_REQUEST = 0xD566 + GET_M_QUEST_EXIST_UNITS_RESPONSE = 0xD567 + GET_M_QUEST_EPISODE_APPEND_REWARDS_REQUEST = 0xD568 + GET_M_QUEST_EPISODE_APPEND_REWARDS_RESPONSE = 0xD569 + GET_M_SIDE_QUESTS_REQUEST = 0xD56A + GET_M_SIDE_QUESTS_RESPONSE = 0xD56B + GET_M_EPISODES_REQUEST = 0xD56C + GET_M_EPISODES_RESPONSE = 0xD56D + GET_M_EPISODE_CHAPTERS_REQUEST = 0xD56E + GET_M_EPISODE_CHAPTERS_RESPONSE = 0xD56F + GET_M_EPISODE_PARTS_REQUEST = 0xD570 + GET_M_EPISODE_PARTS_RESPONSE = 0xD571 + GET_M_TRIAL_TOWERS_REQUEST = 0xD572 + GET_M_TRIAL_TOWERS_RESPONSE = 0xD573 + GET_M_EX_TOWERS_REQUEST = 0xD574 + GET_M_EX_TOWERS_RESPONSE = 0xD575 + GET_M_EX_TOWER_QUESTS_REQUEST = 0xD576 + GET_M_EX_TOWER_QUESTS_RESPONSE = 0xD577 + GET_M_MENU_DISPLAY_ENEMIES_REQUEST = 0xD578 + GET_M_MENU_DISPLAY_ENEMIES_RESPONSE = 0xD579 + GET_M_MISSIONS_REQUEST = 0xD57A + GET_M_MISSIONS_RESPONSE = 0xD57B + GET_M_MISSION_TABLES_REQUEST = 0xD57C + GET_M_MISSION_TABLES_RESPONSE = 0xD57D + GET_M_MISSION_DIFFICULTIES_REQUEST = 0xD57E + GET_M_MISSION_DIFFICULTIES_RESPONSE = 0xD57F + GET_M_BATTLE_CAMERAS_REQUEST = 0xD580 + GET_M_BATTLE_CAMERAS_RESPONSE = 0xD581 + GET_M_CHAT_MAIN_STORIES_REQUEST = 0xD582 + GET_M_CHAT_MAIN_STORIES_RESPONSE = 0xD583 + GET_M_CHAT_SIDE_STORIES_REQUEST = 0xD584 + GET_M_CHAT_SIDE_STORIES_RESPONSE = 0xD585 + GET_M_CHAT_EVENT_STORIES_REQUEST = 0xD586 + GET_M_CHAT_EVENT_STORIES_RESPONSE = 0xD587 + GET_M_NAVIGATOR_CHARAS_REQUEST = 0xD588 + GET_M_NAVIGATOR_CHARAS_RESPONSE = 0xD589 + GET_M_NAVIGATOR_COMMENTS_REQUEST = 0xD58A + GET_M_NAVIGATOR_COMMENTS_RESPONSE = 0xD58B + GET_M_EX_BONUS_TABLES_REQUEST = 0xD58C + GET_M_EX_BONUS_TABLES_RESPONSE = 0xD58D + GET_M_EX_BONUS_CONDITIONS_REQUEST = 0xD58E + GET_M_EX_BONUS_CONDITIONS_RESPONSE = 0xD58F + GET_M_QUEST_RARE_DROPS_REQUEST = 0xD590 + GET_M_QUEST_RARE_DROPS_RESPONSE = 0xD591 + GET_M_QUEST_SPECIAL_RARE_DROP_SETTINGS_REQUEST = 0xD592 + GET_M_QUEST_SPECIAL_RARE_DROP_SETTINGS_RESPONSE = 0xD593 + GET_M_QUEST_SPECIAL_RARE_DROPS_REQUEST = 0xD594 + GET_M_QUEST_SPECIAL_RARE_DROPS_RESPONSE = 0xD595 + GET_M_QUEST_TUTORIALS_REQUEST = 0xD596 + GET_M_QUEST_TUTORIALS_RESPONSE = 0xD597 + GET_M_QUEST_PLAYER_TRACE_TABLES_REQUEST = 0xD598 + GET_M_QUEST_PLAYER_TRACE_TABLES_RESPONSE = 0xD599 + GET_M_QUEST_STILLS_REQUEST = 0xD59A + GET_M_QUEST_STILLS_RESPONSE = 0xD59B + GET_M_GASHAS_REQUEST = 0xD59C + GET_M_GASHAS_RESPONSE = 0xD59D + GET_M_GASHA_HEADERS_REQUEST = 0xD59E + GET_M_GASHA_HEADERS_RESPONSE = 0xD59F + GET_M_GASHA_LOTTERY_RARITIES_REQUEST = 0xD5A0 + GET_M_GASHA_LOTTERY_RARITIES_RESPONSE = 0xD5A1 + GET_M_GASHA_PRIZES_REQUEST = 0xD5A2 + GET_M_GASHA_PRIZES_RESPONSE = 0xD5A3 + GET_M_COMEBACK_EVENTS_REQUEST = 0xD5A4 + GET_M_COMEBACK_EVENTS_RESPONSE = 0xD5A5 + GET_M_AD_BANNERS_REQUEST = 0xD5A6 + GET_M_AD_BANNERS_RESPONSE = 0xD5A7 + GET_M_EVENTS_REQUEST = 0xD5A8 + GET_M_EVENTS_RESPONSE = 0xD5A9 + GET_M_TREASURE_HUNTS_REQUEST = 0xD5AA + GET_M_TREASURE_HUNTS_RESPONSE = 0xD5AB + GET_M_TREASURE_HUNT_WHOLE_TASKS_REQUEST = 0xD5AC + GET_M_TREASURE_HUNT_WHOLE_TASKS_RESPONSE = 0xD5AD + GET_M_TREASURE_HUNT_INDIVIDUAL_TASKS_REQUEST = 0xD5AE + GET_M_TREASURE_HUNT_INDIVIDUAL_TASKS_RESPONSE = 0xD5AF + GET_M_TREASURE_HUNT_SPECIAL_EFFECTS_REQUEST = 0xD5B0 + GET_M_TREASURE_HUNT_SPECIAL_EFFECTS_RESPONSE = 0xD5B1 + GET_M_TREASURE_HUNT_EVENT_POINT_REWARD_COMMON_REWARDS_REQUEST = 0xD5B2 + GET_M_TREASURE_HUNT_EVENT_POINT_REWARD_COMMON_REWARDS_RESPONSE = 0xD5B3 + GET_M_TREASURE_HUNT_EVENT_POINT_REWARD_TITLES_REQUEST = 0xD5B4 + GET_M_TREASURE_HUNT_EVENT_POINT_REWARD_TITLES_RESPONSE = 0xD5B5 + GET_M_TREASURE_HUNT_TASK_TEXTS_REQUEST = 0xD5B6 + GET_M_TREASURE_HUNT_TASK_TEXTS_RESPONSE = 0xD5B7 + GET_M_BNID_SERIAL_CODES_REQUEST = 0xD5B8 + GET_M_BNID_SERIAL_CODES_RESPONSE = 0xD5B9 + GET_M_BNID_SERIAL_CODE_REWARDS_REQUEST = 0xD5BA + GET_M_BNID_SERIAL_CODE_REWARDS_RESPONSE = 0xD5BB + GET_M_SUPPORT_LOG_REQUEST = 0xD5BC + GET_M_SUPPORT_LOG_RESPONSE = 0xD5BD + GET_M_SUPPORT_LOG_TYPES_REQUEST = 0xD5BE + GET_M_SUPPORT_LOG_TYPES_RESPONSE = 0xD5BF + GET_M_EPISODE_APPENDS_REQUEST = 0xD5C0 + GET_M_EPISODE_APPENDS_RESPONSE = 0xD5C1 + GET_M_QUEST_DEFRAG_MATCH_QUESTS_REQUEST = 0xD5C2 + GET_M_QUEST_DEFRAG_MATCH_QUESTS_RESPONSE = 0xD5C3 + GET_M_QUEST_DEFRAG_MATCH_QUEST_BOSS_TABLES_REQUEST = 0xD5C4 + GET_M_QUEST_DEFRAG_MATCH_QUEST_BOSS_TABLES_RESPONSE = 0xD5C5 + GET_M_DEFRAG_MATCHES_REQUEST = 0xD5C6 + GET_M_DEFRAG_MATCHES_RESPONSE = 0xD5C7 + GET_M_DEFRAG_MATCH_SEED_REQUEST = 0xD5C8 + GET_M_DEFRAG_MATCH_SEED_RESPONSE = 0xD5C9 + GET_M_DEFRAG_MATCH_SPECIAL_EFFECTS_REQUEST = 0xD5CA + GET_M_DEFRAG_MATCH_SPECIAL_EFFECTS_RESPONSE = 0xD5CB + GET_M_DEFRAG_MATCH_GRADES_REQUEST = 0xD5CC + GET_M_DEFRAG_MATCH_GRADES_RESPONSE = 0xD5CD + GET_M_DEFRAG_MATCH_CPU_UNITS_REQUEST = 0xD5CE + GET_M_DEFRAG_MATCH_CPU_UNITS_RESPONSE = 0xD5CF + GET_M_DEFRAG_MATCH_CPU_SUPPORT_LOGS_REQUEST = 0xD5D0 + GET_M_DEFRAG_MATCH_CPU_SUPPORT_LOGS_RESPONSE = 0xD5D1 + GET_M_DEFRAG_MATCH_PERIOD_BONUSES_REQUEST = 0xD5D2 + GET_M_DEFRAG_MATCH_PERIOD_BONUSES_RESPONSE = 0xD5D3 + GET_M_DEFRAG_MATCH_RANDOM_BONUS_TABLES_REQUEST = 0xD5D4 + GET_M_DEFRAG_MATCH_RANDOM_BONUS_TABLES_RESPONSE = 0xD5D5 + GET_M_DEFRAG_MATCH_RANDOM_BONUS_CONDITIONS_REQUEST = 0xD5D6 + GET_M_DEFRAG_MATCH_RANDOM_BONUS_CONDITIONS_RESPONSE = 0xD5D7 + GET_M_DEFRAG_MATCH_RARE_DROPS_REQUEST = 0xD5D8 + GET_M_DEFRAG_MATCH_RARE_DROPS_RESPONSE = 0xD5D9 + GET_M_YUI_MEDAL_SHOPS_REQUEST = 0xD5DA + GET_M_YUI_MEDAL_SHOPS_RESPONSE = 0xD5DB + GET_M_YUI_MEDAL_SHOP_ITEMS_REQUEST = 0xD5DC + GET_M_YUI_MEDAL_SHOP_ITEMS_RESPONSE = 0xD5DD + GET_M_EVENT_SCENES_REQUEST = 0xD5DE + GET_M_EVENT_SCENES_RESPONSE = 0xD5DF + GET_M_GENERIC_CAMPAIGN_PERIODS_REQUEST = 0xD5E0 + GET_M_GENERIC_CAMPAIGN_PERIODS_RESPONSE = 0xD5E1 + GET_M_BEGINNER_MISSIONS_REQUEST = 0xD5E2 + GET_M_BEGINNER_MISSIONS_RESPONSE = 0xD5E3 + GET_M_BEGINNER_MISSION_CONDITIONS_REQUEST = 0xD5E4 + GET_M_BEGINNER_MISSION_CONDITIONS_RESPONSE = 0xD5E5 + GET_M_BEGINNER_MISSION_REWARDS_REQUEST = 0xD5E6 + GET_M_BEGINNER_MISSION_REWARDS_RESPONSE = 0xD5E7 + GET_M_BEGINNER_MISSION_SEAT_CONDITIONS_REQUEST = 0xD5E8 + GET_M_BEGINNER_MISSION_SEAT_CONDITIONS_RESPONSE = 0xD5E9 + GET_M_BEGINNER_MISSION_SEAT_REWARDS_REQUEST = 0xD5EA + GET_M_BEGINNER_MISSION_SEAT_REWARDS_RESPONSE = 0xD5EB + GET_M_EVENT_ITEMS_REQUEST = 0xD5EC + GET_M_EVENT_ITEMS_RESPONSE = 0xD5ED + GET_M_EVENT_MONSTERS_REQUEST = 0xD5EE + GET_M_EVENT_MONSTERS_RESPONSE = 0xD5EF + GET_M_YUI_MEDAL_BONUSES_REQUEST = 0xD5F0 + GET_M_YUI_MEDAL_BONUSES_RESPONSE = 0xD5F1 + GET_M_YUI_MEDAL_BONUS_CONDITIONS_REQUEST = 0xD5F2 + GET_M_YUI_MEDAL_BONUS_CONDITIONS_RESPONSE = 0xD5F3 + GET_M_GASHA_MEDALS_REQUEST = 0xD5F4 + GET_M_GASHA_MEDALS_RESPONSE = 0xD5F5 + GET_M_GASHA_MEDAL_TYPES_REQUEST = 0xD5F6 + GET_M_GASHA_MEDAL_TYPES_RESPONSE = 0xD5F7 + GET_M_GASHA_MEDAL_SETTINGS_REQUEST = 0xD5F8 + GET_M_GASHA_MEDAL_SETTINGS_RESPONSE = 0xD5F9 + GET_M_GASHA_MEDAL_BONUSES_REQUEST = 0xD5FA + GET_M_GASHA_MEDAL_BONUSES_RESPONSE = 0xD5FB + GET_M_GASHA_MEDAL_SHOPS_REQUEST = 0xD5FC + GET_M_GASHA_MEDAL_SHOPS_RESPONSE = 0xD5FD + GET_M_GASHA_MEDAL_SHOP_ITEMS_REQUEST = 0xD5FE + GET_M_GASHA_MEDAL_SHOP_ITEMS_RESPONSE = 0xD5FF + GET_M_RES_EARN_CAMPAIGN_APPLICATIONS_REQUEST = 0xD600 + GET_M_RES_EARN_CAMPAIGN_APPLICATIONS_RESPONSE = 0xD601 + GET_M_RES_EARN_CAMPAIGN_APPLICATION_PRODUCTS_REQUEST = 0xD602 + GET_M_RES_EARN_CAMPAIGN_APPLICATION_PRODUCTS_RESPONSE = 0xD603 + GET_M_RES_EARN_CAMPAIGN_SHOPS_REQUEST = 0xD604 + GET_M_RES_EARN_CAMPAIGN_SHOPS_RESPONSE = 0xD605 + GET_M_RES_EARN_CAMPAIGN_SHOP_ITEMS_REQUEST = 0xD606 + GET_M_RES_EARN_CAMPAIGN_SHOP_ITEMS_RESPONSE = 0xD607 + GET_M_PAYING_YUI_MEDAL_BONUSES_REQUEST = 0xD608 + GET_M_PAYING_YUI_MEDAL_BONUSES_RESPONSE = 0xD609 + GET_M_AC_LOGIN_BONUSES_REQUEST = 0xD60A + GET_M_AC_LOGIN_BONUSES_RESPONSE = 0xD60B + GET_M_PLAY_CAMPAIGNS_REQUEST = 0xD60C + GET_M_PLAY_CAMPAIGNS_RESPONSE = 0xD60D + GET_M_PLAY_CAMPAIGN_REWARDS_REQUEST = 0xD60E + GET_M_PLAY_CAMPAIGN_REWARDS_RESPONSE = 0xD60F + GET_M_GASHA_FREE_CAMPAIGNS_REQUEST = 0xD610 + GET_M_GASHA_FREE_CAMPAIGNS_RESPONSE = 0xD611 + GET_M_QUEST_DROP_BOOST_CAMPAIGNS_REQUEST = 0xD612 + GET_M_QUEST_DROP_BOOST_CAMPAIGNS_RESPONSE = 0xD613 + GET_M_FIRST_TICKET_PURCHASE_CAMPAIGNS_REQUEST = 0xD614 + GET_M_FIRST_TICKET_PURCHASE_CAMPAIGNS_RESPONSE = 0xD615 + GET_M_LINKED_SITE_REG_CAMPAIGNS_REQUEST = 0xD616 + GET_M_LINKED_SITE_REG_CAMPAIGNS_RESPONSE = 0xD617 + GET_M_LINKED_SITE_REG_CAMPAIGN_REWARDS_REQUEST = 0xD618 + GET_M_LINKED_SITE_REG_CAMPAIGN_REWARDS_RESPONSE = 0xD619 diff --git a/titles/sao/data/1/AcLoginBonuses.csv b/titles/sao/data/1/AcLoginBonuses.csv new file mode 100644 index 0000000000000000000000000000000000000000..166c5b2816a132de06030b4b07b98943e2b9b430 GIT binary patch literal 283 zcmdPbS8z=B$xqMBbIQ*vE%r>&3CS$UN!1BTEl(^;2~I5uE=>Xo`WK|;1(zfimAE99 zq=I>_c_|<^FPEW?l97Rt!PA{v%%9EauzWUs>HEF&-tXyn+_dO%bNA!sh41&YJ#Jq0 zxM?$(-1%a~jHmmzzTeZSq+sN53qT*4Cr}XOE$Ihh`ty6~Ab5tMe-Unm2YO`cF?RNh?K55d|4!kocPmFP^w^!exyI+ z>!&JWMDLMiAq&!|al~zVJln~UX2WakGXXj&g3bP9%{1G1a+OL$SP#5(aGMh-5R#Hu}NLtLGzt^d+6RCA!{Hz l^ZjOrDLkLaWGxzUg4#fBx_74Ut#tq4WNpu(9_ zrXO=U^Y|BC#~MV)-Qk??e2;SyiG?ji$se8+cI9+dK27Fwa;C6PJ+`gH(iE5j;-SZyOWLb_$Jqbh<&YiJ#9AYLyc2;uW2`SOxX~oCB{57i*LN*MO_j z^kMxnHpP3PuP5LwuW5RAU#zTC;t$F*;eh>%-}sZxr zqS}p8&(V;?xq(^0-$IZX?ci__Tv@`kB<>yD)qGv^^$LzGtm@;gF!%d~@JDkbegGcb B9%ldm literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/AppointLeaderParam.csv b/titles/sao/data/1/AppointLeaderParam.csv new file mode 100644 index 0000000000000000000000000000000000000000..173f312b6467eaa121bd7277005b17d2a6e49d0a GIT binary patch literal 166 zcmdPbS8yyS$j{6x@kvcgNi7OUEK1DvOwsYo%Ph%E%n7c{OU@|D4@xXa)j?6`nU`FY znwy$ef})~4F*`LcGcO&htXqC&j$=v+iUwq>LyAg^OPuoaN{e~940VhRbPRP&bqs+N GFBbrY7CbZn literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/Awakening.csv b/titles/sao/data/1/Awakening.csv new file mode 100644 index 0000000000000000000000000000000000000000..05d1bd0fec4015c519f38e7d49e9c2d44254d2f9 GIT binary patch literal 159 zcmXAfO$viR3`X~X+`$8cI==@{5GuGVbUn~PO6x?%iXPu)X7NJ4$7|cBJHjWXcbdl+ zJ@^;C!?{w=Y2WTK^Tcx<&lpxt_3!e+89|d)iquW{|Iq@1Ayb@8BOq9^RY_(A1V^qo ONt}S-$yX&=0pS8r9wxs4 literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/BattleCamera.csv b/titles/sao/data/1/BattleCamera.csv new file mode 100644 index 0000000000000000000000000000000000000000..d4410da105dd84c055d38bf12d78c3bd5ec99a88 GIT binary patch literal 162 zcmdPbS8z%!DalE7PRva$O7u+8@lQ)DPA!Rm(veU)N+&44!~;Zx>9}MTmn7yTr|S5n zCKlXFo=*{Eh87LWIhWc*cR4&O`Zv6Qo%GU7?ijWsnF*DqI(BZ){f z*tZV#t;5!LRutz5LXeoYE6Oe*#+o)&$xBRswbi>32YCKr3R4o&npz<*jFl+rT!&IXq3x zPNUq;WPy|*O@LN`nZUjQt;-jhKq}$|PyS6Hc?SaG3m_2t6YRndx$r}ppD)#I;Qx)m zAP6c6MCm0Fc1eVhM6 zXIDDqo~8<;bjQKvJVsbv)Z;GdaYW5pd^!K`=SWXn&m2LlZFPY3QAEKqoc(w$h&urf zpPzg@KVDNbg2=tZCR}0@Vwj*O@aiZC(v{s|c~vfi*~{V=a)oWx literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/BeginnerMissionRewards.csv b/titles/sao/data/1/BeginnerMissionRewards.csv new file mode 100644 index 0000000000000000000000000000000000000000..8e24ce5f92a0030feb9c00430334e70cb4bf706e GIT binary patch literal 812 zcma)&!EVAZ42JKK@(z0dOVn(G-Ou#M{p$YU729QK^=H$De;E zv)S9y9%|>T-_+-G-MXEL}Q%KG~E)9Hk-nv8g z16$votnY3(B-}it_`N?~EGC*pO+%w#C}Dz*Og_Qn2@`Z=%miZ+CTP+M0W<#Y5g`XG zSs6g!68{rXD-sMvhNim16s>sj=)*ayUQ-?nq*XsAgL~A9HyJS#t)8wo7tA~|PuE)r kX3;an|Ibhersx^W$UNRh+)b-eFy)Ir?}Fi+)k{zJ3p7a7(f|Me literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/BeginnerMissionSeatConditions.csv b/titles/sao/data/1/BeginnerMissionSeatConditions.csv new file mode 100644 index 0000000000000000000000000000000000000000..ad97ac70f60d81e53fe8d5863862f7c65a9e2a31 GIT binary patch literal 419 zcmdPbS8z&A&&5UB(tDc2X0jb41mSz^ErZ^^NWTuv- z<^oLz+Q-XfXlP(yr~@Pn40J%Wj*{i$#wCv%*FEi+`*eTvi-y+6&GR2OH$QIL{nB{N;*o98|OZ5>UrEa4@|B@ z*Wb7rr2oaj70;LKdfYe_tZ3Qe#+JuT-5`Gg&41e8^rE5pF+?FAcc9n~bPUjp#>rqe P80sh)09^vbhP+$=LfOb$ literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/BeginnerMissionSeatRewards.csv b/titles/sao/data/1/BeginnerMissionSeatRewards.csv new file mode 100644 index 0000000000000000000000000000000000000000..c306a39b3cee9e42c66d0534c101523f9913af0f GIT binary patch literal 401 zcmZvT!3x4K42JK6y@L-B*xK$1+{uF?PP`WyX2G_j>yW*@nWawa3Qh7Q|5sJJJ@(pK z^hZ68+Sv|exZqQIbx@Gq{x?gybyn#>DxF(rjI+_d&IA4|vioT=&;^gS4|h0wH=qwQ zO9O?|$Zqm6AE!!sxH>WB?TWKQJOX*QfDb0YJyV8Cj6z^Ktov@jR6f%sG9 K#5KHYS-b$G%#7dw literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/BeginnerMissions.csv b/titles/sao/data/1/BeginnerMissions.csv new file mode 100644 index 0000000000000000000000000000000000000000..56cbf191ab3241eac5b6ed7a7da7d8d18396185b GIT binary patch literal 121 zcmdPbS8z&A&&;E9od185mmX8ye^v7%G@s85jT&SkM?G0unTW I3i5IR0K;!6{Qv*} literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/BnidSerialCodeRewards.csv b/titles/sao/data/1/BnidSerialCodeRewards.csv new file mode 100644 index 0000000000000000000000000000000000000000..556883d91ce130675883d6f214320f6991f0a7f7 GIT binary patch literal 301 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 SE+HW#Kw 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
    tQ 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 literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/BuffItem.csv b/titles/sao/data/1/BuffItem.csv new file mode 100644 index 0000000000000000000000000000000000000000..b4f3fcdb03e74bebec7e4b2b697930570bbe4750 GIT binary patch literal 417 zcmdPbS8ys#OYT;KchTeK=Eu#` zA2-efN-6Pj83DC7Pkr3H@^RCK$IS~LH*W@-`Mh@%(1e%m{c!aJZNW4Iq#c`$#yUz* zJLW!{GauyOrnQfoS3hoE^SF5?hyimRnsqOybw6F+{kX9k>?sUgjXm%8Oa}V;apNk8 jr%ZsJ0P}Agz literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/BuyTicket.csv b/titles/sao/data/1/BuyTicket.csv new file mode 100644 index 0000000000000000000000000000000000000000..58f0c48fa6302b400f7e13b279c3666b5f80951e GIT binary patch literal 300 zcmYL@u@1r@5QcXW-oXdBq});3y0(LHb1?44GBhS=S~~dl-T`75_BZu+&|al z@$~ULL*>goM!yD_RzELs3K32|X03$r7544+)(XJ@@-bQwBk^Ihj1D?L!=X(=QXFGZ zc*shh6}ghd`XEfe6YB0ggP literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/Cap.csv b/titles/sao/data/1/Cap.csv new file mode 100644 index 0000000000000000000000000000000000000000..2d577429d71e3926110591040d3ead1954708eb4 GIT binary patch literal 27 gcmdPbS8z@&@J!JODJm^4F$A)CxeRp-4Geg>0Bk-6?*IS* literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/Chara.csv b/titles/sao/data/1/Chara.csv new file mode 100644 index 0000000000000000000000000000000000000000..00ae23ee17bdb3819b25903ad43584513c0f7944 GIT binary patch literal 945 zcmZ9L-*3`T6vy9}?0+zQZZh%rd?SQl(*~0g9`MD~t+ECv38ODQjo#?k#14(iB(ljI z%+Zap2r{UP{xO%9_P_9)Uc|(NhvwY-x!>>kp2Nz@t=NWAQiOFrsT5RRE*2C%q3Y{u ziI-I+9~NW9LP6Eb%S#cSqh^m1o02NWbD~z#%0-U9&2!#yosyA%b?ttrO&tdgV^5xo zg3K*1-9hLpH7AtpLOr@^=nBWfQ?nm+_lxCyvb=+i+rbBciKf!P+yw?zZYbHO<}n;e zs`8Ry3#r-Xq5pn|VNyH_*gcfGro^IT0QHon7xhreYaY%%UisbbbV3-A5|RMiXF8@s zNrMu?+LE-*7ixKfdF;2`p%rY`g44!aNk{?sV6J3S^E_0_YiskaM!$k>Yqn>*z4~7% z35bVC^eO35v(4;@c~t{@ke++uOr8Cpa^tq`fLII@r8r=Z5IdvhB_${n&1(4&!AQw~ zlASQ84n)PMXhHyL75h4+Cj5F(&lpBdWBm*raOXA8W~1M3?}Ra#G-e(FBjKy()EuHz zDGRfOIl1FE?xE>5-_8}}1l|sJ0X#qg6TA%psNwbSjd>LZDxskWZUH!giJD_-+CapO ztjd<>zXO2c=}-fkCl`yrWCimAVee?*UHE>w?swckQOg;{91IymSkbpzQ>XTCV1kGY?i4IL z?$kTLQpPJoXUPPuD&2Yw;k*5B@BxRQluAECY86H{9dG&73oOGQ4mTIuso1n~0JY@P HB<{l>yqb+D literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/CharaAttack.csv b/titles/sao/data/1/CharaAttack.csv new file mode 100644 index 0000000000000000000000000000000000000000..95d3fe490f2936b5e9b3522f30bd5af049bc0ac0 GIT binary patch literal 163449 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 literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/CharaComment.csv b/titles/sao/data/1/CharaComment.csv new file mode 100644 index 0000000000000000000000000000000000000000..28363831fafb602862315504601a539c4caab6e3 GIT binary patch literal 35771 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 literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/CharaFriendlyRank.csv b/titles/sao/data/1/CharaFriendlyRank.csv new file mode 100644 index 0000000000000000000000000000000000000000..0e39ed18a16b8c214264ab6c4f6aec06df017d93 GIT binary patch literal 913 zcmXBSO-da=5Cq`8;2k_bfz$upYe^v6-~}EiD2mF%MRNPhR8|GE_~_}b`}@0xm&^6? z^ZNSy@$~-p_vPd5*ApH;zc24U{(Rlu2-fY5Le>t0o^aTSfS*Xji9()e)QLf#kcDB& z0nWpgNQps+i9^UqKnO}gNJ>G7N<+xXKnUxCP*yoJZCMpcS@)Eb)uEI%pp-SCl(nFg zwV{-Cpp_&&28BgPT37k z*&R;V_jW0J!YO;fDSN{y`@kv3f>RDT$JlZxf^rywayWu=1cGwx%TbO(P>x1WjzLh) zg`k{r?z`nwB;_vp*apIX>G8d`pYj*F_TJ}yz%WrY62px4I_K=Q*Is*HzWeTX-~WAl@{j+T zxHtLZ_`CG$xKO5B^wmw9icRAPp z{jL0%BT*hj`4ZJXtA~ws1R7NzY@O%QIK)sKe&pSvg9D9zV}gC7yW!JD&MhPddJZ4R zfsRj+{fm`hb=s(XK6lQj7e?1|Db^<3@6CnnjlrAVaDO3Jw0V+D zxV$Bi!JyF@9y~E>#i)i+Eu$8Vp3ixiXq@P{B^*081{Mb&yZtjT{)%|li$)#NGh!C$ zbWW1uU8ht4f~C`=+Ul;+JrgV#g$lNa{g@oWajJ}~l*+*MyATqX`NRn~!3^CLSC|l- zFd=-WLT`imjDE(TmdN5tcri|-?2D|C1ij=rk`5P-qA3tCMR$~v>~RZ$D#BH`{UmGw zvmY)lgqN>#Zc3_cg$f>100u1sExaN*1hb>nxv&dd?DnJt3(CBlX3G(6fs zOB%Ih6d0aoL|P9|cp`*b;HyRrrt2cr165N}jpFIa;a8t4L0;R;`FxU?H~KXL{srzP$k#8|5O zoofGLnQEty>_*N_OI%_xaj4J}9qw_W(CH<;LC&rSteVnR5$R4K~g)ln5xw9#n&vrlYCC>rX} zN?p=(%EF1Y-BDwv-#HxaUq%~e{V!{y=K0t2rNr+jn)=Nv2!j#_Q4dbFQL}V%?9-l8 zk%Px1G+IMV!N?|6g$u(8q}f*FWXK_3C?MBi?Y$wd*4$rTsHN3m_Td#_p8!53ajSp! z9t^@bqZ9Nq5Tt`^C96mc2;`1AMW8^v(Lgm(Eu;7NRcVPUQUkukp?14P%~gXVA$|eD ze10}*(xpf7po-(C_QScgo7&5znj*M(jZBbk^`oW|2lWum)Z=_1$mq}PU6?W$k~E!C zUqmo67SnEj395GXE54 z`L}`X`F;;tXl5QG>xFN3w42CZ&OAXD4Br~*w`{cVaa-`*Nu)T(4yu^7aGdCH} z2qxYU9Gk%232c|Cz_5WTn_0#SEOL&vj9Zb@D8qQ--b@?@hZEl%p732~aZhDZD=@oq zsO{k9$Ea2{Rh9J>EI1i&$Js!!CHyYa|n1L2&i1zd)MLC;HOVqBOM=P%eTfNcdOSW}!-?@ymib6d>EcJH5 zteM~dP8uC|cygn?<};2^loAlkmLOga;E+%x6^`5~nQKUf#P0J&wj7tua0v@Y(*(O$ zUc?okKwAMaQPK>yeHkLuN;dgq-n+6B_|}A3IOssAtsZV+e}uat2V4gZ$g;BflT`sw z%Nte&YycxQO+%Vi5MBak;x^L-7Z;LGFiNP4YIEWFXG~@+yZD_l(~Ftbl)ZrgmbBQy zklb3ohi1mS+sSYtn}b{-q(#SgJmiJVcney58n=ss+1N6cv@N9Ks?Zb%&H52-Uu6rISOZ? z1kiwM!6AOZ)|-AidANoNj z6@UYu5D0(brJht41U zC-%~7WL@&@JiEz&S2A`W+mdfb+P4fCUzcPD_|#zoh7oXAqpY#7s&;xXqNyr~j=g}s z7tTq7KG3f?L}m-ee#G?IQ8(DY z8p|TnG+xMAR6Bg7)8+$!83amnjU{|=x=Fe|fm&$&=8r$<8efbVT?5EG5>R+Vo1<_0 zXE0&Fcw0)g07Nr`6m1y{<;zLqNo!b6-yKAT2pR`x;&L7135a{Kn^&F-MKH{8Rr z%r2bngt&%@<7=z{HFOTvU^iRzrvvoH?9;FgRb)R@0A$(-XlYCS#1n7jpddS00D*Zp v1-l#A_O4=n*9WWbnFlP2Us&bIw~8k#T*0mgH#x{^hGtpe3U*8QhJ*YMH0%5K literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/ChatMainStory.csv b/titles/sao/data/1/ChatMainStory.csv new file mode 100644 index 0000000000000000000000000000000000000000..542d3b61aeef82a56777ec0faf0efb10db1f555a GIT binary patch literal 2392 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 literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/ComebackEvent.csv b/titles/sao/data/1/ComebackEvent.csv new file mode 100644 index 0000000000000000000000000000000000000000..2c8e775e37d3edf810cd0ff6e1165fe81a516714 GIT binary patch literal 417 zcmdPbS8&eHO-)Kn&UP(J%`5Rt(Lvz{mnH#)Tr!IbauO^35_3~^f>O&9i&BD9OCVA~ zsfDGPMX4@{mBl*2C5c5PE{P?nC7D2lu6ZdiHZPZ{e%w3@?7q!PI>tK2#2IKpq=6>H`W66*l$qoJ literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/CompositionEvent.csv b/titles/sao/data/1/CompositionEvent.csv new file mode 100644 index 0000000000000000000000000000000000000000..a221ad538b681425c6f5e74966e1518025742be4 GIT binary patch literal 129 zcmdPbS8&eHEyyph($ literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/CompositionParam.csv b/titles/sao/data/1/CompositionParam.csv new file mode 100644 index 0000000000000000000000000000000000000000..7314322e50d3ad7429a8ce3f6769c72ddca728e0 GIT binary patch literal 104 zcmdPbS8&eHEyyp5yR smM3PX=4IxkgLyEO!KDQS`9&o@`RTk|hB}4@20*N1pa*6e81Qld0JRAsXaE2J literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/CostumeType.csv b/titles/sao/data/1/CostumeType.csv new file mode 100644 index 0000000000000000000000000000000000000000..bd1ce898c09687eac9d4c2d62e6753ac76cb8773 GIT binary patch literal 209 zcmZX}y9$6H6adgY=pUjv=-qxv2M2q3qfvq!r6?Qv`)uv5=cH-W?w7l*!RIhyOUBz= zPPz4T%-B5)vLqg7t`)+BnsM33VUYh#t3}9#!{oD|Ie_<)I&}C=MWzMxUK3Y1Gv0mI Jkdq*oj~Dc*I!*up literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/DebugValidator.csv b/titles/sao/data/1/DebugValidator.csv new file mode 100644 index 0000000000000000000000000000000000000000..edaaf662823e0f03a21b6abc3989dbaec3b46920 GIT binary patch literal 356 zcmZ9H%L>9U5JmTb{~)uzU=pjMy?bO``?8(?2upxM2)6*e zxf-m_Yu6AqAVuMQ2Ot$7jewa0`v@HV>u+3CcH%H9RJ=xa?n;6NX{C%3lhIKyP7ub` r@@__f*-XECDbM?%h()0#O@vRTm{@k85QZbE>^YkuXpBn6G;aF^Oxdw3XdemDN|Y7Yf>8D@+{L3#teEIS3Uw`xG+y8@q|L-5c{PD|wzTSTL z??1oZ{_u~#eFN~jFW9CWSZbmnkEp200dS9s1cn^&(jFBpka7@=iU^h*dsIZQb@W|?7~!sCwRaS-Yl z9uI??57)tp*5R_m2P-m|zs`yl80UyvVnqSgF+5mNfWyb|U`6O=2?sS#Ruo_z!*d-2 zJXy!^jEcDY@G(4DkzSC;5W1hNXn{V4Co3|zj^W9Q0$jSIqLUQ`xO69Y-FQ0L!zIzAaL_S-W??9eNv~J&VXKim9BT*z;V|*Y|7%h#FDU5oM4}^-i zUV5M!NEJps$_HXaLsMhqibR{n2o{OfsUAoc=ex#;7EFaN8Y5d6EsYT_5^WkIT@fR=U`CW{Y92uN? zGj*+#fmgsRXP_S_gC`FRzCq3O`~AUBYcu#pTUi|#?#E)<7dDva!TOz^0QjX{Z^Q3n z2gr(iTl65-RN^BOJqV^^ADQUEo6EpN4}xh<1Cu?VGb{b`ZP^1c<+$y^k8$jJO^Zqh zV4??}BbgGwL=ViQ`@lpGf@ux}6Fmr~xe!eBprr=9o_#yz#JtPv(}4_kkW6>tiCT)m zII;?-oS196cTPDmneN0>PBhb;c*=>%bjP1^B5!HUNq=2%*Hj^yH5KKMQ%*D!?p+L3 zFw;DG%86#06HhtOOmpHXCnnRKc*=>%bSIv2A{lz8{Sjz8<-}yV6HhtOOwB#4$sW8q z@stxYZ$LMm;OK#zXr^Z1cFKv3u@g@@F`4efQ%+2#JMokgKV+NbME(hoS^Rw_PB}4| z?!+l4Ces}<Cu9|XUGTl{EPBhb8HRZ%)+SkuE<-|-l-BnXgG}Bx) z<;2F=RZ~t(rn_p&iOF9*A>H@=)l}t-s zV4?@HgOrP`$#m~ra#b?jJC|JbA*G=-I0T=EsE~b#TPE%~DhCFSm}wIU44yaBCK4Fj&c)P3 z0t4@uX%h(yZogvcZUh7Gn1L;Fl7oSFz`V^WFz}9AW6P40xP2Hzh6Fqo$qEedIwEGlm=)_E1-HA$RX4-v>qEedIbSEmMfq8eLQX0?Q z+e}nSGt-@@lxC(oL@BM9HWQW7%ycIzrJ3pORZ0W1d=TEzyjDsx(3{=X?mNcOQkfdi5|q7K9Q8tYE8QvmD0exdq*j)nC2a&w9eQ&N@-@gca+l1 NbnhsofqC`L{{UG~I@kaJ literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/DefragMatchChat.csv b/titles/sao/data/1/DefragMatchChat.csv new file mode 100644 index 0000000000000000000000000000000000000000..6aa812e5eb6408e0c3d5497636d5e8643c2a5358 GIT binary patch literal 760 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 SRfAf&{8#~du?Qk>fc+mr6`d#m literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/DefragMatchGrade.csv b/titles/sao/data/1/DefragMatchGrade.csv new file mode 100644 index 0000000000000000000000000000000000000000..b81e7bf82d2eabd7e386f7542292001b13f471fc GIT binary patch literal 22852 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 literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/DefragMatchPeriodBonuses.csv b/titles/sao/data/1/DefragMatchPeriodBonuses.csv new file mode 100644 index 0000000000000000000000000000000000000000..eb348f52cd4d802bb97dcc9288bbc0db6ee92f36 GIT binary patch literal 134736 zcmchAO|Na+aaBK3{)6?T^se;I{y69ClO_rc8j&mkKfog9qmdXH)1ZgmZP*AwK!{jD zLO}u1AOsK*2_I4TF;~WNi@(6AT6?X1YStKY)|}@V$&r&8scO$!Rij4L8h4$qzy8i| zeE)C$@OyvjH^29zzwx)f`~4sOoge(xU;4rC{QbZ8SAOf$yZ;CNFF*Zj|KRU_|I>HB z_rve~_G$aC^Y&ktPk;4C-}~W@e&c&T`u?ZC{5!wp|NWca|Lq_A>aUzWoj*N&`s(!b z)u*ra|2aRM-oAc%`TG3)o!4J~dcuG3|2z&~zH>efzkL1=Uq1i&mv8>dm(TzD%Qye- z%jZA&^7$t}|0npwtJBl3{q^sD^{c;fhMN=IFfT96%PaNr&HwoF z&7XYv<`07lcW{A&^QW(_`yIkheq|nR?>zkMCx7~j-~VU-=l8$)#qa$K{Oaq|mv8>) z%QydiyU)-5*Ps64U;Y^`@d^(Yi$uD`>(@`G0iM`6qO;ypxY8=N&x0&pV`Y;Srs# za6+rc&9ya(!#5f|T|K8~c;MyZ*0PJ!(>oWn@;SZ0g?c)$N1UAAIa#QmlT|F94nc+2 zuTQLEPqAWqPv^QPomD9+h&y3bdJ0wAt)Sh>*(ykt>4{a)DOOOWOwadbK+6>N$OD>c z278&FE!TL{!DC@%dJ1J)ZaP++qHbW3Tqo4#;YQcZ*(y`j&FRKmyf4jK-JGpBW!=0H zC+GLYxllJ}t5gv<*v^4FheE9c&MH#{4%Uc^_mx=;+`WH3v;KKzm3a=8*#+n4o!Q(Q zD0jjd=(*lNcPAHn9IAm5n)dqjd8sll7x+;Ro)zYq7pAV5%N6!j!-O^R;6qV%pUKW? zE-!GRD(1{8=JLUcxxB)odO$FquXW)iBY5-RJsp@i_0yBiKe%~X@%*{GU&IE3HAPvp1N`}B&3 zP+a)o^*Jb>=U(v;iVHtWMfz+^O(q*FQn1o@4WX87Ryn+#;&-#k`6vN>wi;3%2?M=- z9U18SApc?mmh{LvD+uKKKu#}rUaW+rM^?fOJDvAv#VkFtVvbaGA*y;kf=8CeRvd=1 zE~V#-)f(kQFqcUy4tc5Rmir>-Avcyg?l~0M0fHS5)&$iZSPuk?eV2U|4_3s=1FPXk zi7OtgXoUwtfp1?63aq2_YDKEhj?iChvFBUst8RI)m+KOTOLX*Aw>&r5W)%*XTI;KB zd2W*U2{m{;`9aHj)h!QxGWEsbQfqy+V&qi8O_KZYdLP~AzC0w`clgPz6^EhfOW5_Q z8y)QBvV3bu={zLgbfa_QIeZO0oo+if-RR&kw^kf}u(jTFqjTfrj-iAXqH4v=TPr-~ z){4VW%nKf^tT`6CadO8{)}{2k$;JmCxwYbumzsw3n{0e;jNBp=*#S-+53=#WLvF1& zoNcW)+4$U8zC)zPg*&`i(bCrX_BElyI1GKS;=zhkr4w1m!bL37-m8**FZb4Kd0@9B zQlUzYdkmoat=IAh?Zjn5ZIu1=S{_-;!p~C6d+W74LUG}T*WoB`dKKd)$@AgWIPzm9 zDU*%WWTd+#>^ePJ(I}#j3F+ynVT;u)I6WEk!%-GdY&M&)*edMxj)TDR=8#Cviq|Jyk%D z>%8?Gr3x}fysL>yJsd&0(aG&ZP~m89Yx;0xZ){W%<+Z6f;yrG7qh7UUI%01A4f8{^HD-`iP~4o(GtU!*937Ww(zD+YYiW9fI6=wLahaH?xeOg} z<(5{4hOIFp9t1jFU!jevsvigqXwU(q%eAs)jtpCG<%H+}xdC6&( ze>z*_lnd4x<2LnZjXf9E6_pFt6yy4|c_R=vsvD_+4_doHY6RkO`} z)t+~(*2*w(I`?`YHn6#+M;3WJk5R|C^m<^H6W}^xcRF;`>w%Q9iWTb6QLhK)Me6A^ zNsW)L4nc|P>Ta`rMmXe^KfICECQmuHNgORSTL?=OK*LL}PaFoix&Y8nmULX^dWFl6 zHd}~HIxcgwh09pJ^PTUaJD*!8k1r7Cg}_2~(z#`Z5B!cmp;ug5eaj@x&Gyug=~}ae z1VsQm?4@-pYFqHz4!}c$qFv->3m2i-4&?2*ke$%2<%Ea})shR@3E_ejb7amX7goKr z*}?@Y-pG_oF05KrQspJBJ?~hprDprAdms$5YKheJxXB2}p={9|Ba01iok&RQj&u)n zn>oY8QgwJEt4BWB!VxP-!G4F1^wtKBSXl}VJalBWsZuWQaqSs}^-vARo~?Fd+*t7l zraHXN8!q!AM(7Id`j7taIR26Ss~-GIn9o1Ozp(KW``_`;cuo+PbR4h@i0`dG4;`4W z5uwT=WW+xI()t|(z=ZLkINW|)n_Bv-z zWX!&X5raHM=Pp@cqH#{GFpu1Qy_b7dWg@p`D4syZT{3%u7RJhi{0l^L#5zhOdNrVj z62>|gTVcWrtNFGWzIAF4rgHK&s?Fi=?=)bte#Gw&`-AcCx9Sr` zLAH*kcI!C&{k^_RXzzJ)3HT@qU43q|eG)iaW2cbGh={pK;wP>pMiG0n(xXoV_H&=Y zVdSPm`?BKP6#4yOyEx1!=x_Az&s_;l!zyuXUskl6BENsGqTj5dIgGRDQBnwQ zI*xKff}>d5gMW^4l*XH^aW|PG*1TOGW|Y-9r&ecME)oZUk}goIH>-B(G7r1>9iiIi zU7n%z>A1`^6)r>JuedZQkhf%5@KMfshRjHDK3phItT+ttP$J`JXp9uSaDyacB1jBU zf2SAcC%X)#O7N2zEDj@B?0z&=IzJg+C>IPa1bywfFr-i}7*I6KwQ>u?2<0M=t!K3m z?cKs)Lb%BK(i2<+lsI^P&xOGPpIF>n&a1cutz|FdN;-Q9X@%>61jkJVlkxe~tADZr zg`>a)79a;-x6zQ3Rju}vV*O9QmODej)N#oeLtMeG+|sf{0V^2`aLa%TD=rNtq)Ubq zaZ*@uspofGh~Y*Y9*Wg)pWn%-2rmpe=7b9emY)niRB_W$A_^CZ*SXIT^g4}d;S9wi zHj*5sM#T+L6yeE-$@froKMV|J#FNkX(2lkr>B`-H1gQb*J40@z?MH5Mw;!-+{QC_S z^5Z^ikpM*`s=RVMX(Jmsw-2L*w zrRRdugu@!S1s_>@E+|d7yNGbI9X8WSf$TukB?HyWD|Z*X#5=cuoJ(hKvpv822<2S^ ztBch*X4M9)4uko5#F^yCsu5V>+d!S`uxf86ky0^2$R#1AnLX}T3>80w6nqy*+h?^t zw=h3ntj5#o=KfRTv~fR-H9mq|D8saUR^ZL3F|>bg<;@%3`)`ee;eN==Tcp#zkvK#A z<$22;u_B+-UpyRHrHd!h%pBW=nweu1G3dLr{kVp>+YePf%vgL*NZV&kyqPqHzrWMO zn*rng`{$ZCWMcaF$8Eyh{#->vBc>Cn`Nwg02UG3)NTjQkcHRmeHZ#)3iD87a4@aFE zZ(b}jwS;%SV z-*gfbxXTDQ@hPQF5-=!Os`pg`V|VL>Azn16|gk4P!5 zfeSHsh`YTS*T9FQJoU-G6<#uR#$g1BL5&^nx^9x9yGfFn5iE8;9a4i2N(tTs7Yr`u zMFo(`X%`GA1Y_>p0%|$wf{yvqypbsE9RNOEZ?eW?a6PxmRFAh;WBFNVV@UccAC z1wuN38g7#K;j~_(#sw-m?IJgJ_~qr?b77D{vCplMTSQVnhSv-9bJ|5NVfc-_zT(1Q zLfs;FZMX;}d%#gIPgbHHWp^f(N-u;NnB9B!T5GT_9D zOM?x*`Gd<$0B{&^qlV@9Dw(bZ!ujEaL&vBZ@GUZlAB6S8&oqAE+hf{A-VVS;fRCfF zI}io^oOZ#mW18?^PI9Ke56*=DGOr@OKsKjSi!grph%iwCT8QU5Uh<25c!|hR^I~A2 z;GEEYcnM%|JoQlmAa~J66w0%YsBGVtO z7cUUcY5R;LL|Q+#Zv{@*i-->^@W|oE_V3hyNNV7Km3I{T9?w|h(k@WAKs@J3%N?;I zZ(bejgl*&q5;_?%@(SV7{;7viMnOEM?dKA|HuJ{ti9pTv!k;)ssN4f_7<%B?zA01rRuy?tu8rnSG6kDJ|R_ z@e%0r0uzGLtrI&|E%8NFF?Mh8q8dr_X_k#UFHW`4yNR5}Sisn8^RjAGlhC`VoQ8vm znRWy&*Ry;saFnYPhoL%a&js=~k<)Ik%{Lr!g4*f3P{662c5}o>EY9g&`vqD!odm3v z&ArLGT+KQMHe89dvbibuXk}N!1JRRVGKVe>xkH4;Ujx(^A#boik^@&QBlOkkpCh!} zBsW>0t5Dp@Y%Ywh$89><47r`oW+LUn?E+I>KxoMCWD*oc)nO7qYu8Bv#$4y7)FYcA zx6>ENT^gYaKrp_DmAxuU*dj^BJM;*fA-C&fGpqkNsn)O=T05Q1toWy2PF10Sk0#}CkO>S91ArYMIVuv!-c&WZO>&b!VCs(SdC(>aKN-Go7&*)^VpfNo zySgoMk%(J^dGgq4zi28BM;X6x81SnGD-hPHnsUYqsWLJmUDr*hD(w+{`6kvvp$5GpN1!iv{4Q=b*s_uVOgLaDl_XDvA*_tl&84 z2FZ0IR?Js1gnOMF%p?Q9BLGJ|f_mh4o8)c*E@}~kg0B<7j1{lVJh702Q1s~tk`V(g z1IZ{y5V}5{gRq$+7-x-Q1Pv=x4w9`2t`o6hJ_n)m>qL+N$J`8ZbQ0+NIuXn~19uB> zQH-Erg^VD>ia3F+2>Nsmk{!$I%1m;Fnos8-nMvR!P>cB-yh7clBS>ZvxD3Ei&A}_ak=KbJ zgNuckEM9Po>uYqcmGZqTH zj-!knw`MY_JCZf-CUeA^ab)yFAe^(DYW^&NsWg1U)7pQT`~v$G!ns%R+RkmY4lq4V z%~MBdLqTWXk76<*w@DlYuqtuc>-iO4s7we>GEm`=1C@RWeLdgcLJb_hl*P*xqPkA( zGH&5o0k>+ggQCt;fvG2-JHzo5{OeBwDEdS~f$&C$o8GchbpbsP{VA z%<8{+l{>%CfRs-s0Rw`#LK$W=bbOs8U?4EJFgYL%>OFap!oFmD5i5Hx6T;BQ)~az; zQqN|qf5kTn3zA|sTk#8QCjWE-SBwM8-ZFdHZ1u1B26 zplZV;fT&K}&nyl0BTE}WCPa1GeuiY&k56T2Uy^b+)e;ySh9@$Pk0#}Ai#P~KTrLu5 z=~Oks5gRDyHrWTPKu)KhNJeBhpBpTprSppr?&`Kk!~zo=MQnemamA)aqeXGD7vD%KBn!2M-jT0peE)@IyAY#wZ z+i5U|t(;PE5*b}&UynRPZ>QT~o}X?-p8Jyf3v@Y#UvBX_?w75pLII9HTCe!Cu{w6-^)Fn8dM(E83NLC| zP~hc9@CHlMXMD^~<}|22c}SsLhdGUJ*y)_Eu<$`~K63iC_guzwy!c!U0_bZwJZILo z8&1C8rkB%(0&*l{C=hx10)j$4r;~s+@rJ06SOP?IItji-g8qy@4l6J6EfDl)e9*3Q zkpe^CpT8)}Bb%Yl(@aYFqh)ro8M-`?&6~NVUD{!6{?qHSXDH}+5}5JS*6c8jq@!$xTu$3h9nI{= zWFFdA)wr1=5u2Mpp_0?TKjko-Mk+fbFi^>fBH3)^3&b7skyfK>hf1#Fh~;nOvUNvR zjSN=oLaB$@VaVZO#GsPX_Su{nSIT4iR)p&JC(3HSe~z+35~uB_EtA;~9c%piI~AR! zF=l_RqVXNOP9m|A79@3;u~yo&5!#PV4ayEp2MTIuT?=zrt4`zw1Ph zm3~|>uS8G|;ub-M29dvxQ27x-Lj}5%F-NS?$K7)=2PGXhFAN(ZcU_6#9VK`UGL&d& zY&i!FC1eB{Ohh_cjG!FP5kbmj<3H|Ph#*9Jx{9+T=5%Uy-{l;HR8K|FZMJZM!-!C& z6)|*rs*2qp@e&BgN(Aq7JIO(~%PjDGoOSOt$3@&&TyG@cEVIV0+(6E9o+3Y!GP{LuLE-Mkd4-!0r8J>Q#k0>b80k`OF4oD7b*wy_TBCl z;G!5o!wQvydG~H!B33L!(13%!R@khQ2nOIN;~>7v*NGs5i#X&IBWPG5BbeC?A{en^ zA%X@RGJ*^^=4PIQ5906Yb|T2&A`U&Z2tuc)BSY2n1=8|eApM?m@1qD1ZjuEGz;erARJuz;>K4TZn$TM0ALYSxR zGi->H&iMBmEYJlJem_HnIOB}%-!UMocPxh(2Fy(~$KD(YJuemRh+#mSXhx-SID%A9 zMvTlf_#56~|1pdh6ngdnkgS|Qz%WAO8i{N3vHdgb^Gy^Se4O7u0(F5S#SFeRevdEd zz0tSK7A{tRk=`GVz4Imm-_U!Lt&_Z0faAdD7YH!ad!5*^x{R+HYIux4lOGkR1qwd`wik%@G_Ym)4Dg|O88H}n9!X$$AP}0o zlz^WA2jh!a@hgPR${D*zpz6o-7iIalh}FMBX!245j^bor%VsNnrUSAc&Wb;h^W!$X zY_|GW!_6;+n!i97D3hSV%m>8p1&TnO1O-NZgl&`r&;{y?Fgg%9`7oOy0_Ktiv(zD*>pfY zRDY@(;V2K3IE>A!T8)tZ>4@bVhM(BOMo%27KUM8+i$rXpcR}^1N)cXi^Ex++=!H^* z3ecYrA?0p8Uo5Vq4}=lGzmxJ8e5OxFkZ@Ue4@FIf3*i@%~9_EE(M8dj(X61od7 z5i1rVXu!eW9=P?4gK;HYuVRA>J_W%MgNw+3YY~KGu@gat6>$^2P{sJlUnhbLIGW{j zwThu)>_m{kMch&sBWPG5bC6-hhiNh7jhzTG;F#N9A2^6F{&gbA;396XixJd=#hK}G za2T;-zKZepdvyd`Jq{M9_Mi9v7PVZ(&^^)!?luElY=;q{O1&8BN1B7XLE`13Yz9(D z8o|wF15evwoNcOA3;|>(f(=4#j>3>r%R#6gI}vO^Y4h??HUlYSCxQ(;Z7!!~o}-HK z=Y2a7Y!GU56o#H+1jS6U8zedSQ8t556m}xmz?1)uW`SSMK{1o;HuK%$@KcPSm`OG- z%|9R54kK2~;~+j+$j0H_IJsBAjo|_Tx<_rFfkZR%jK*!q9kqRi4RO*L|9*poyujJw z!%!j4IAi;F3~1jFZ4MX)%uO`Mf(0KS^ituD7zV_NX4LQ=jtmA=#2RX{-7=uSIAT`w z%04UkW`G%Hl$E-$-_9-m`{Ud){{2?u%08>{II)cFcPhHw&u_nfuA(7L)QQxNC7Xk( zzJDYV)QKHOthVE1QqEXt67>^dHQtOPqm06z2=>2LM5Gfw^#+i|Kd@Gp4*z|zB&0Q<9o$F zZtaAxf&0y8pnNqR0o@_o$Q?xk_M4kP_>TCEwmi@t!c#`G-6l!xIkbn;aJx#|w>A$A z-uq#kKu1?OQX5jk&apM^!@=Vx`5f5vP2OkCOT%|#u&3idi}oH`^h{n@&Wp72G3Gbze0=% zHzKLq7iOLj&G@%L*qBL|jPbPV-yvo5Fz$F5qNgQ z9tN>9E4WeexWX`B%S1>PdqK=N1l3FYa_M{~;^T##Oe8cLKBw^F$VBKEJDEs$HarEu zC+fe18@|B=%^us$WSA`-B~DS zh#PxBWDwG@?{W|wI8&!waHvcquZ(=hKOe-- z*x?p)!p9dzD?xM!A)9uG-1Z-LY=?n()Pv~2A%n<(V?oUJicc~2GLfOiN7Zbv_&j4T z6ElQdCfWRvi4Z%&6Az*rZBqn?3*8w4M;S!7NZf?dTF69b9c2*VGk0ek27+JCMEp79 zUJ!G4hNp$@42@$ih`Bq%O(?CEAa*7eH%}g1Ao%qlb|w~h%5{drKnqAn4_z2PWuf?=$nQS3NiMcxqYQLOMaMr=gI(Wa- za+H}sUi!mjoNq=biob>o2a@$MP*`L#CU4YNVZboSWK7%Gxe9}UhRW!4jN14$#?xGVbna0yz9TT|7NrF#uZ;rgd4sXcZ+SruFYz2p(ahtsK||*Cq8)x z6;J6{N1CKj1reSyt;S)X)ip+7Dn=DVw@5A&P-Z2F`@}#PB7A1x!69eZ!ytAh1~*D> z7xn_hAUaC$OoW?^LpTiEft4Wcc)^31X*PULA=4uhJ9C2@B|(gP!csZodyBnH%(!%I zc7rQHbO_h(Kv9pkHizF)|YAgiNAw)G6^6no$ zBtS?#hz=YwhzvO53{(uFUQ6JoGtg+}puvXaX8K7nmB0ami)Q>8I&g5Hi*Pt#^kQ!8 zxt$yngab15JU8RWXc)bjI#P{@ZmdmY90oKPCDlHR-b@qO@sx)Tc$&h1hbO&*aGT^x z0RqNB>kJ$4kcSN%hUy=C=nNTdjNBpAdaVLY_^7BI+~h*XVPFXJ{P>3NI&OJDSc#h_ zS6dn~Zl--yy#Sta{o^pwh02g|!k0}&|x7HO$Dij3^z}L7=fZn?M(aF2_gfGxauzku`^`gLU}og!-yL5 ztOOr;>;;iQ$lU7xnAGrv$6gRKU%>4Ga1?{6NrM|CNfAI}i0vkM6s!ae7+f?1#fX)d zzEO8tGDti(gG9Nfo4!$Ag^Ut#6tH0!D^oUV2aGc`bHgZBCS&ZxD$fhJ!W0G^ti)uD z`YH?p=H86lOu(oeu;OpVgJG=pQYWvj5f)bJaV)6km7WaXh*kRJWMDc)9V^BRCMN?E zC3fh3o&)#S!@T~C;|5XShW^{4=utC-&4U2gRKvf8GLimb{|Y(1eXlQ#KKg+vA#V6 z0M4fSx+WD9d@u-$EM^Gz_`V_>a7hF_FbsZoMVvo;oYby8FkJvjBA+8Ubmf`GclvgxeAvVbu|++>YS^PnEe*w&EbL6FAfWQ zf#=L9qff(T2A&R_!^anKC%(XgCx!-*)zom#RE;|4V3AxX>_HZWHd8suoFhw6Cnii8 z+RHfy2Z3`Ho-@oj#{`9Q6@oL!gh$RfEXbT=u+T7_fu|$qOxP$2)fqvX3pfm|b=VwC z&!`>brV0mPLojyWNI)OE+afoJqrm8~n~YK4AeRfghH@F-!0Vppl^Y{h2n&F6|1|NU zLQQze{S${_b6EG}1+Sh;Zl7=zrv7ozyxKWfx-k;8F!9&G(!Ry0z!Gk9w|x2dn$Tp7 zisiWZe6>YpZDK~%ao{O`Aq9t_{A)op{h|&cVb*XHKwzQcn53~2#N15bGvY!OLQUJ) z2_gfCNU)1Rbd=y!5x+gNY#auZSP9~e7i8=2=1C?_;lL3y= z0(=Gt8Fj8Bh^B+onMhbTJO$t=2C=hkaErOL;%0H7obBt5oggv@X_k#uOtjBDc7n)= zMqD};gXpNCFp)5FL^jZjl^}L@5N@74xBwybOzi9+;E4f8+(s6I*x5n6uwCS9vnreq z;+u&eRVKRmHi$S35K;}|o9Q4`Cc07LDF8<)h;Jr+RG0`inQq`PqQ*2PzS+khJ3-9E z13u$qQY_~;`yON`h#5HGDRPg>JNY;JBxEOu88zT0(2bQ$bO_<2wwovU5g??ViJgfB zo-+5qVE~R|CUzzkx0o-4W&OKd%SsSC6U(a&M8xOPyMQ;-LF!B-6ARo2;HU)=uTDCd zNG2AziKsDO&TxqT(2?1#lNbgB8I8lndaN7}R9JP!lyG=h!Q3xMcYzNT`0b^Ja3!i)xK9N1_+# z=P(mY$mp3stA;GbakZFh11f!VE|V{Ia*lPDW-~tUbmW}L7dtt}I?M{+#Gj{z8$N?0 zO02(pm~WbVQRdtX-R$L@gM)y#0?{1joMQri?E^ZIUyV5nd`HyFNNw6hopVHH0#A`z zmUGU*fNvn(Xs*=AF>5$y3PzoC1){ky)ot$H^(9q63UW2Xo8~*xq=T2k^2%Zq5Ht=*{_q7KJl?fbGt++AbAr1r8Sed|4b!vFZv<-&=pKGXT0!M|KZjoFl>Va z-)=PDt|4=!MX$*oW$hCFc59`>quMo%qjr!7J2;pF2NO7Ia3JIj9@fCYl#co;3>zBm zHjI_S0!3>4=sdvTFrdN^sZH0|i50_u#kHVG8+EK01Wc|4O~ouke~GL5{o)5riRjv$)UQ3cVBw)l<1fJwC=nz~U1(Jc};0g+aMxbF@~CcP#fd zX1Ix{F)ueTouk4;H=iHe+yrxEqW$^mP7oPz#1W<#L`MxuXmOuB$KWu4#*jmqs8O&$ zw?zUL;9~4x2M0p_;UUj2IGh59gV5Svt=3mz*f2TA*f$)tgS@f8O#&*6W3^)dj}^m! zx&87UtJ9N-73E#XAoDgk$iRc1FWgwptY(lI%op&mS331agPjI8; zGLa^XFVm}ZGRwg2RHY+aUh4eaoLeK8h_yc8I`6`zR|@dNYCeuGqe{VLdc{uOkHBpp z0tFAK_Nx$5fCS`y8xBMN8fDksOK=&~{Z%L_+-iO@3rdR9lL-y4;dYge3b=}WZqOngm}ja)op}XnTg*HYC+f^2vI<-&biEbKJ3Cki z%$wo7z09+}N-Z&u2rF>0$rflIWz?8QlofCh8Sfw+9*n`h#n{O_1`u=9_g&^WET|ElrOb1%5SceaiF=u6(nXnhWV4TpMU)t_Vbd<^%p)6OxLC|Q z6EEt^u*8cS;xT}mXi<8|%i+^gyQm$9YiA63656aa1kw&esvWIP2$LR#YBEKBnawEi z6r0ibG7bf-qnd|AQtfayyBq;%N8PNEOT=NTjB6%S6m8C2C1z@evHeumzNaVq(xPZ{ zn@Mt|Ku=e|#w3dZw9RQEs&*J#&>)Tv&^D!FCyt4znyUZ^MbI|2VkeFX4&dkGMAa0G zI*ysCn$xh69hL99I66|$=C@hRc@p_*HI5DwwE3Bz^m^G215PaEqb3oXeR8=79rb+F zB*I*!vDOX)gjD0GNyKK7Tq)wm!r)|oX1bG)X@6tCB!EdhA5HP7>pHVoa}^+@7)KL6 z>blNU*8D{LScs!(AZ4$F`TXn}PCM+CCV!M3GL^-x=JY71ClfmgCbn54msq40rgqeq zVUW?RbI0N8QHxAm?Sc^>#vZ!VBJXj{MZkqo={%U2ea=ybi!qBvi88*7~RN({Ro$-8+EuCfoLX*@nw4Do|Wzh*X$%=-)+?4qNBu`)cy2i z0!JCHtS&j>3%QY!jKihN-Ho-tg2MI%GZ`2+#u)ss$72~nq{@k=G zMz=aK*4kOh#I)3oM z|73FeocD1gSsDCI^{C^RSM30kxN4usM~4u;Kyj<&egQ(NadiBUab%1l&MHfBbTGkU zl-4zwUU1_`G6r<~T|PR5$T%_xiQ`K#j;40hbxnpCTq#hEl{lLAv6GJsOyey8*F5MugAK@;6rTg^C}975i9&OunU zTO%$4V2rToG2)s6QeP%R2HXeAG3LA`fz%!{*?@Y4?y7g0 z%q-w3Aj2?RP>1w9WEg;lxgq3a8c0VP9icD9iU?))){+1xj%Ca zq|IZw7;!jNj7J|knFjKiYaq>4u0|Y&YOM9KlW8EIxdzhw6CfbEbjRn3tvHmJR_RMy?*1XBx;(<}qAY;qE8XKz1^ZLBof+`^hwr zGV{o*3J?>H6}`+e4Wz(4x7Pv^4r4nXwm{QBs?2k1#6|4mWy~`Tq{=+FD)9G#t9O~_ zu)wEpxEd4rBIeo08&&4PPlg5@1_H5ydG_^2k$G;OJh8wejsoswo@pR;=4BcI&k-et znP(bEoq1%l3w{@etwB0GxT_NbMoxH$22=G7i_y7zjCWYx%K9 zVs||Dkp}W)8c2Pa4BK!Y>D>4-4h6KMy0LthSuS^R1mGITGuzYRkmK_>Tn+*XY{+&N zt^!II!NxR@8f*xWgP#D^D_~<9NDbPA)xl|Cb)&R{x^KrKyifua#pjEGH(?HU? z-mMZ>5w}Vj@snvFXdQ1E>E=EH#W-AI2-%I1y99hFgi8$}n+paF&73pHu6N-QL&)Zd z;XyO*jKd{{kj(>wf!Q&~RFj?TYRHQFZRVVMxV-K~F}AXl%~RkIg=gO@_jbzJJWQ6m zsU~%}+5u;C5$nAiE|X8nVr-|H%~LG-Aiwm2=!jeI-)Ik<2mX?fs|li}`4hNtKUovh8y?3`n%XPfb6m;^_8?(A2J{Fu!Gcy_^}d42NM}b1}4qiQ|{L$r|iU$K}Z~c zs&RDuka1-A5vQOPd^DYLlw`~XA@caVdN=g&S-07l)xt3Lf^NC*g8y z#6{?^^>A%EOBybC%B6zC&|}A!*+iAfL#`Bfh?O$Tu1!%%-jSncyEPK7DK7pf#!XtO zE|W_I?n5grgiDvZ8zXlKB)s4O<^ICd!cKA8&jD?R(M7d2pY3~+FH9}GTWxQH^T6(l zEyX?|`NGt~n;+Ji8)`pKanANhN!%}~h23X?<{w4c(J!4%QTamD!kd#uU%MSnf{h6& zJsk+x_>hpFQKNt#q{E|nOh~CSufXV!kO?u%L9WlHrPP_%sCd1yXo6Ii=3Lj6yA;Kf>6{JX2Ka%wsLPA@D=oHX&sv^K!4oJpwu$_YmpuF7q59 z@JVypKj#TLS0_K^VQf3}`vjNJ*iAgW>Oi?K_kG#}? zlSxiuLQ0)^-zt4KMWx0(@^-^1_xnGrI)K+l=GmXa)|giy^2N+EU8Tl6@=n8v_q!{t zVBW>SLUh{-uiwMi%{+$*MelOA1tXj6w)UY(m3gq1F%O4}m}fdmQATdG?dz9j-8p8G zCaRPk2m^wLxSJTZ2-8yPaFMkpT*N1`KlWjZFgc~ZOcrUtQ-rqhWgH4bb`kai50kiN zfBCu-E;ek0K09NCFurbCzZA0d|GXsSsaM?#0-Du6^Wj;5N_`AFyx z`~;*}iKD3|bsPyFg3|~Z^*B0sa9ZhBbDl(2RE?uUh{{Jojo>^EP7AcsJ}KFWBLkCW z$ytx1Lx_xH9-eT&03p>lc2*K@l02`7A1iU(*#&u<3a5-+L@9fH%(1_t-KlE^A&m;8 z^j7+8+DTp4MEG&}@Zan_olQ=u^N~nEuI(1NoR21_RB?2xZJyvT;#M(^okfM4Bz^)^ zt>B}5ol?aSPV;&hhmrEs)Nf7`$gO;#?kSE<0JfJsKVigd5;M&J8{gQ1*ZYD z>UC}3uk7Sw<~?u~ajO`|&icYllIQhd-ebzlP8>66UE7(coR6l=)Nv%k5nQeC9@A$k zd~}mJj&VX-scZX_-eOjQ&pa#PFks1ev^24%_P~H+c2+WFrso3Ld*k=!SqX;$Fh(`? zn3YVKsV~DIBaTXiaOuSbem290DDfzG*r^2%7sG`(4^4ziFD`JIOk!{tkYRiolV0iv z!eF4;1`lU_(^=~5B4f|B8GGvCGM%No3>kdjDG-Q4xO(MIrW|;fEce?^6livloiAKW z5rs!F+D9mLxLDc8`KA!AUb&NvFYXc;Lcv3q50FhSTrBc|TbB=zWiMQ<@PS*G50KFX zenQo+^f6OSYCb@u?AO(Qg?l#Dq~-%;j(KiIisIa9s!7EM++@oKa2ViVDUJ>h{PmjK zCr%?mlzEk@CRH5aDl-Ed2BfIQ(P4t~d^bt16zIWH9344i95YkEFNx4mkE25fpO@S! zxnF>gY8)LuWE?YZfb)nS3wsb#O{%(f`{Z%~Oh(=J*b`q&HQC9>%q5W4c zU6XkNe&ReaAIFOcDRmsl>lZi;U@{R$hY*#IWTt?t03p>lI)12pByVBhC*sFS9QRp+ z?B(2k&d0e~<6S;FgvdBD2#Lc+HI9xSGLCuVz&#^=ti*92LU7i2+3%T!!P`Ws_*Ca50F8uUZNoc0i!n zMW>Vaji7qCHf5%`40*Nk(#%1H%jk858*B3yhXHX49`?E?;c{!lMF5+LaOrggJY|Hz wVW2C8%jj}4gA{s*(?N$9jJ;8EC+cz~#jTqm>J*`=|&-Rw+5I7&}mL=Pt3Fhmjq z(Z~S}O5%?xMSh0w)GphO9ZvSv=KZGg^qc3Kg@p$lF_`k<23P%^r?HUgu}?aIoP>OD zlaB=1mK%{THY1XU-M$zLR6HLCs>2nhw@>cvlSfLQT#cmM;=NFG;~v&M^7%yR*JoS_ z@+2Myaz{+pLp~&H(P$*bsv{H+LV5q5`_OSb(z^Nd`s(t{@6&HrKVGq$<6mj=j%8mD zT7){JDB12Z$s@KqU_RetSjF(&9Ir)M_K@D0y4ZoZ4%wQuAqnuE$EUYo4*P|mR023Gbn$&L;I3>nPyF_E-eHDO7gniNQF18K!T(lWpNlAWCa2DN}e zMFF!rvh*hx{ok+zQ?nE>OBEP;$7HeW<3XCd1r&;!o-n;dX~w8iqfZG4e}OhNsF4P! z0@aIDVp?w6BMM2YOjeUhSSw8%MWJg23qx+Zt=aW^T$0+U;wC`p%=FpV{kL0NZ&7xN j);To?YThXTRDmo`&6%2aiW*K)yHj(l=A8n@yj6by_h~k9 literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/DefragMatchRandomBonusConditions.csv b/titles/sao/data/1/DefragMatchRandomBonusConditions.csv new file mode 100644 index 0000000000000000000000000000000000000000..47c2e0167aa1977d66b495f07ad7782cd7dc0305 GIT binary patch literal 965 zcmbu7+e!ja6o&7E-XZ3yV1rKQZY!cLgs3N&(#i`RFuN&&ac?M7?BF30t;`CIvZ4c> z>tWW6lW);l<1~l_MP1FX+56w${?_kub&UGvLdv{H3C}J}DM7Cq7*>Nziz8~#>ks?Y z;DncrsiA-pX5&j<`>*ktR6A_M#o#0&685K-VI;O`&=@Jryy z75A!<->*l~wdl5lo3(9o>&+-s8tb6#NK9t!ZQYb}`k#azCg~3`{WW1$EK7==nLjlVcPfXC_8Rk{>OM9#Tge?CD!5IKWL79t1D49-rp$|CXaSfYhl`3J2MG*PNs&`}6Wo#<& m3V~onxTRIB13x0&M3<~3g;~7q=rj^(BbM)KmTMVtW6CFXIS@er literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/DefragMatchRandomBonusTables.csv b/titles/sao/data/1/DefragMatchRandomBonusTables.csv new file mode 100644 index 0000000000000000000000000000000000000000..6c8bf15e88cdd195c68fb4a0ac1ff322f0bb3f09 GIT binary patch literal 10097 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 au9M72W>=;Y%t&YtJ%`LlV)rda%=`zKuf4+n literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/DefragMatchRareDrops.csv b/titles/sao/data/1/DefragMatchRareDrops.csv new file mode 100644 index 0000000000000000000000000000000000000000..50ec74db2cf8ed58e7d8fdcff554b4576199c970 GIT binary patch literal 100597 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 literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/Enemy.csv b/titles/sao/data/1/Enemy.csv new file mode 100644 index 0000000000000000000000000000000000000000..8f5323f5e59972c5170459380211a0cb8baa4ad4 GIT binary patch literal 3606 zcma)<%W{=f5QXPf`3@FisyP4UeZK=JNMDY7tde#`jaotes=N8ulVcJ&o7++ z@Pn!vRA;NfU^r;_a3-gP+^yxblDnPU{g%^a?$&c!I%8xlm8UUxqC1|4uxz60|(IU%4fGZ z`gVqIhfN>euYE2_-qO!G@d1;EeYE!@)LHo)4%$fSj`JnpSGjW}iD*fUF#hs~Ld6_b z)!dl0M37kB7GvqQJc}hfw?sRDSjCCM=egT52@sOtA&@4P2#k~yzPg+fh2Uuzo&t+o zyocNL)Oqa~x!|9QB;1PFN_05@-X(xrX0-ssXVsS6l^|L3XWfheCJai3$?uVVxE0TX zd1WTS-1Vcu6`_)55RM_iZ-j|R#%P@2WAnV2J0x^9cVr4PUfNI_hQ<8{zDR&R0tL}w!bQP+35jzACbPQ~*{*-`yd(4p>ogQ&sjb>|0|zm-^OL zMwAR>5m-^+QC&4vEmw4z6?kx&P*-UR+t$uy22=NVbXKeb$-Ai<+meP7IoM_nPjv?X zzdrJC!@WkdG(v>e+QUqE*uS!K_|vjSy}F~RTAS;A5yeo_sQ)w6*p$(#`9OG+y?}o9 zx4wD1^sSsw=MZgS`+h(F8E9Gr#+XlLp!?}^l6l=HjRus??(YBhtxv8+8|AB z8tb{9t5MzgplJrcWI}Wlo!3zmqGfN|L(B@<3ox0E%vosC{CqE33}eRbKxP2Se5lnF zljcFiWD7LN0&hecoM`4pBj>1LrG9uXfh=*amKK)h*Ox%)} zsRQQHK#Ic9Ex{|M{g}p@+7EFU#6w;&TIGlV4jF5ByE^q^jH&U?<%D>tnA=c-hOOk3 zzR&62XwnA2Z*c|1U^)2;ntm&kGqF=tmkD9C!DLamwap@9{<~^zRkABHkmf?+k=^y9 zOtzu1QT%sNjtVC{R^iZ;GNMOpAFo|&N~LzCc|`4#*|l=wNo#NYDRshG2-5mI{VW9O zlxO=YaN$4~r9^?NuBMJFU>@?PAA}%%5RSIe>FYrLi%8T^Qn+59YA65}=l`({#<^&C zqNw9bIUaH%?{x;_uPmufc+ZT_iK3RtO52qV_6bU1FwfVnjD+Aks*hPm-pJZ;7o2;Ou|a)^|y z{ir4qyMUFaYXOX|N7iv^UapR~OPQa((9(~Bm8CFSXqgbz@uMQ}W(HBlOn9mnb?}fc zZkgWk0d6&}!$cI(KHiwRe`EsK#nL>7gz>3NNa6|x3RgHha#~ZbvU358F-Br>&NV{2 xvGVv8Obre|TyZgmZqp?P31fuHFcc>N^KUd|VObvX;Q34)|M71Y{1T+y^e--NS)Kp@ literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/EnemyKind.csv b/titles/sao/data/1/EnemyKind.csv new file mode 100644 index 0000000000000000000000000000000000000000..e969dbf4570a3da275935c3e441be673f07441af GIT binary patch literal 49181 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%0DVi~8?`%V+5NLwD_i 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 E04S3JtpET3 literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/EnemySet.csv b/titles/sao/data/1/EnemySet.csv new file mode 100644 index 0000000000000000000000000000000000000000..771ed207a94053070b94348872efda31697c4975 GIT binary patch literal 1580 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 BYU%(0 literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/EpisodeChapter.csv b/titles/sao/data/1/EpisodeChapter.csv new file mode 100644 index 0000000000000000000000000000000000000000..834419fb6cb1b705833cdb1a2778fa7ce1fc98e3 GIT binary patch literal 227 zcmY+-K?;B%5QX7=&>f<+ppJTiYSAXTf}l`PB-N(d=hQ&c@_jt`rL4L%A6tmcW|>(s$INAervbkP8i>!Zfo7Ot J1I;qS#tnQ{JM{nn literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/EpisodePart.csv b/titles/sao/data/1/EpisodePart.csv new file mode 100644 index 0000000000000000000000000000000000000000..97134fa848acda3745fface670e1c2bec3866f84 GIT binary patch literal 50 zcmdPbS8y%JEY43!4M;31@l4Ti$t*6&Nv!ls%uVIxGSpEreA&E0iI>YrN684n0syF@ B5F!8o literal 0 HcmV?d00001 diff --git a/titles/sao/data/Equipment.csv b/titles/sao/data/1/Equipment.csv similarity index 86% rename from titles/sao/data/Equipment.csv rename to titles/sao/data/1/Equipment.csv index a7fd263ac4672e89dce0051b44239d6e0c4f3fb5..96fb7cba7e3ad1946a6746eb2b52e170524d851a 100644 GIT binary patch delta 553 zcmWNOZ%hnu9LD!_b-U{7ic}({NS${1tkGt#cQgSW*BgvmtgvzyH&7ZdNhS|Pe z^#WtF`7<-m<^{$WTC>^oB0{WLN>);NL5$yv@4M&wJfH9Lz3?4ZrlB#J;_uu5Zz2qQ z=$8zRX(Azi{C~=9z$8bsnP7y@38@SGCdEM$Vh;XCv)%v}gk*rePz`w0-~b)Uwlx8uJvj$FkrDO|2I?$4)2W=)J zq}lw5Zi@j@v;MDCr7uWD#(SDJDCqo5#a3-iQXW}x;H7LWcr7Oa@?hgQujYn=ck_b5 zN46kvAb%0~p+H4V6%O%P(I!x5&xg1i^UQN9fvY$W;&uI?-3`#`@dryv3c<6R)j4lU z!y#YG=452cjJztJ^MY}=j- zxxM2pd+W;}Lpwk7euEy;ziW_vjf(U7Zq?MwJqq#T-Wc%LK4oWVN(3vL6-RB03TxXR z2p&J+fIMlP=e>h!>-iz|mM@3vAVuvxGSJ>Fr5)Y!y5ppHkMzi}ng(C5O!!VoOXnGx K>%8a}67(PB$kmJh delta 782 zcmW-fOH31C6ooSsuq}_aq(DUrSYB;ykqS~?S}Imiq>2&*Kux zU8sqRE=^d-tlheC$HHh0Q9@9oQ4u8~#K$@R=6rMR`Dfu ziDz+kVo8$$h>iGF=yJj*lwyhHOpMz7Oj>lGp?>{Gs3Ykg0(-K7xi_VZ7);d=VMBmW zjm5-*DU(=Bvk*peE>V(RNepCo0P!$$0XkuM4Q<~10YP<^i+TSR8}nORnV+|=qtu&y zkjYF=De*3M8==@7gfmYLP@B)zods-0xC@tItVIbtV6#^d?GBDDLe3Rv=l18&eAhez z#jRzoD^4cTcQ|>>SMnAn+rvqPd*>ghvvdPmQ1(V~&F+#LFqF&jioAuqv#}zF2vsH$ zBUNVJ8mZ=FLp7E8=K4hVG#;SV z_Q-jIXXJ+(?v;a1oz21t^--UE;aqG%4N#AM! diff --git a/titles/sao/data/1/EquipmentLevel.csv b/titles/sao/data/1/EquipmentLevel.csv new file mode 100644 index 0000000000000000000000000000000000000000..68f4e439b1b4bf1e618e296d94a5ee38ea6bee6d GIT binary patch literal 2047 zcmYLKOK#mT4BS89-Ju5{5KE*a>Hu97=&I-i3iK@sq+hf}kKdW0;{=Ig)8uIQ8T#qz z&D)=UzWn(5`1bq#tc%JSJH$f~gqlVW>vd{d=7jP?j9Qu#%8PB==7iz}vHf7`xB!5UO?MVk=XMlE zxfN7*ynyo<^)@UB1(RZj4MI&`0X2!a zKDM`@yxSyHI||FuGtz2}6-5ifCc#>(%KHEbKC`5JKh+=OfS?kACYVY=bJq$CR8q>b zQ5#I9rCgQE22+VC&t0*>lxVg!S@+upQ^{$2?V1gy($gN5{svPC3av#-2mw%^m{!6TfO``hqGd*)-(J%i(9s3 z61tT4V#7M+EtPbgft^}-aq+`Ph_eB|U|ofWFOgO@Mmb($@j22+a?UQBV|gofvV0Y* zPbBVbUHsaxEWaFAELIvr(3aOTu-3{CyXXQ$FXV{Iyp|9jt;^akUwpeRtG`(P1N{YW A1^@s6 literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/Event.csv b/titles/sao/data/1/Event.csv new file mode 100644 index 0000000000000000000000000000000000000000..18a37edb7eb41afbeb02570bba4c6b2c6c3ff8b0 GIT binary patch literal 328 zcmdPbS8y#$%`5Rt(E-yTl?ACfA(vpB$lLt zd9HaWV0J)$aY<%gIzkdE0F!slNGw6s4HJQ>@JlTRI>RG1rvOMfrZ^?$<)s#RrtoqZ z>KN-NJ#Jp}xM|7b=FN|rwm)v(^SEgjkgf5!x$SY&&d1Fino2rKWMGPMLEh$!Vs40M2ymkR)T>t|vB literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/EventItems.csv b/titles/sao/data/1/EventItems.csv new file mode 100644 index 0000000000000000000000000000000000000000..3842ab5dd951fccd1c73b27c7bc8a05fe50c8b4e GIT binary patch literal 1656 zcmcJPyGjE=6oz}jcaW_IGP9Rx9$+zzDGDmxI(x_Fza{Jubw#rLxe|!3F;5jW4nNp@|M|+ z_u^*SO44|z<(QtR8?r1X`1bY>?+x`6!i@!+IfLQXRC|M9-V`xQPQ@w4e@ zS$yE|L4^-V{O9v{S$yd5VTF%KJUg5^iD!qiY4EEp&ldRt?D!ibo*TcCB%T|;vhFT6 ziyMxz;_3&~F9@g3c0jnWrp|l#=NAyUmPcjF740ZHX4+A9%+}GBwp`JgvOlENba78u G@qYujZ6~Gx literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/EventMonsters.csv b/titles/sao/data/1/EventMonsters.csv new file mode 100644 index 0000000000000000000000000000000000000000..d756ff52d736de857c5c5d68ad9188f45c783230 GIT binary patch literal 548 zcmZ9JI}XAy5JY<~iprjl3l%fbD8d2IgeBK)(I~=^5bKr7F~f=Q&kCjlr6|JM z3Tg>5AyZqCoRBafldRa75Nb)ugbX$)7LuHhFd<7Tt54LDk_lN1DPBc#Lc)Y>?Gv@6 MWI`5w%C;e)FCD*Z82|tP literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/EventScenes.csv b/titles/sao/data/1/EventScenes.csv new file mode 100644 index 0000000000000000000000000000000000000000..d5cde61d1928409879d498d8f4d3decb53c2faff GIT binary patch literal 9002 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 literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/ExTowerQuests.csv b/titles/sao/data/1/ExTowerQuests.csv new file mode 100644 index 0000000000000000000000000000000000000000..0b8a9448dc056e9a371820fa5046ddbee00d2cf8 GIT binary patch literal 4729 zcmZvg%dS*K6o%&}zJodAPT04;)YQa*2{BR53vlUy$pI4Kpu?l0;=l;e7)eM#BGCaF zQ3)gB!(bMzPvd6@cPk~_}f>n9A7&;f8l6*?)UEp-!?#a&wi%AlCAvP0(5i>os z)Zo-0lth;^H706Uj4d@fH3~(vw`Wa>;$BNlPEA4)HEK|&!JKxFZoRnuymcZmb#qML zZcrx(oS4QbQ|?3xDx3y&GD@8?0n$&4Brx&08u)BZ1 zbs{l!b4;Bkb%MZ&X`C|UPNbm1X;LSn)G1T$L<%aLCUr7OoigQ4q@coSsZLA(KK=6J z?&0UH6N#ytW9qb2CkULFmhV%d+=&!aI4#x5sQLSpD0dk%9`Rl{y)vPWf5x zL<%aL*6Or2r`^Nv_P^e3ok&dG98;&YIziyXG)|dvCsI)1v{omh)G1T$L<%aL*6L)G zI%Ue8NI`|uMx8dk_P%_wyZ3GDL}KbBX1|mhb%MZ&X<2)Tawk$y;j~dFqvo}jD0d1m%RcOnH9PO>>5KU1?)qTGoTR5;1zpi;9_qTGoTlsgr+MT?%Rf1h668x`qEE__`4 zMv&!2jgaqBBFv{qM{=3>MPzwNBVNBJD$R|i0PV!*i>TE64pl{0YS`(Qi;;;W~xUK zf|z;8I(}mFBZ-BRG(k0x=8ihtVmSf(|z7i zR2S)YD=`SXGZ2OQIHi)}0KNZQDzMKghk@nO6 z!(HU;Q0m!%?{c)yutD5K4i2Rr96+Ldh7IB_a%w2`)BqCgGi;FRBJI~B14y*bu-UCt z7inK|w#J5NpJ9Vk7inK|w#J5NpJ9Vk7inK|{vk*EgM9VV=Bv6$``oNZv_DAuX}?ij zq zP|fbdUF3XK>iLQni}o2dNOh6+C1<^i(LTclsV>sK$vG13bKYzacag(rsfSViS&sG@Hi)~(Nwn0HC`h!=utBPev|o>*AkjX< fgVUQoy}WyyJ?8%2$?4P2=KnV-+yNpvuzUFr{&Orq literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/ExTowers.csv b/titles/sao/data/1/ExTowers.csv new file mode 100644 index 0000000000000000000000000000000000000000..7b371e93bd62f82ac1153cdb012251dc75053e22 GIT binary patch literal 472 zcmdPbS8%Ne$uCbW@=VbQ$t=l9)d@-k=2-DsCmA8#?wX3$ZE_m)Vyq;_-x&NWHsg(YMw5d{A|TcWHlBTYM!@kd^)Ee zS&b!znx`ETpDx&fqy`v@=wbM5!}e#JHY2OS4CiM{_dcD2@n632pqoXX6+bSI#Guna^P)nLyQpUmPC9lzA_VxZX`sW}Be z$}z<$F)uH*$TNkP%h15Uz)(l&adYG2rj3uAn;$oKJnfkKboRU#oA*3zUHZ7W{c+Rc z$4xsQH?4l$wCr*7W}pHk9VH_JBLjUyBYguy1#>F{10aG38UjW1jm#8`kOeVS8k*`D PS^#wbbs6Xw@NxkFjpJi< literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/FixedFormChat.csv b/titles/sao/data/1/FixedFormChat.csv new file mode 100644 index 0000000000000000000000000000000000000000..2433d0233cd8957d8848930038435655cf68a2fe GIT binary patch literal 1545 zcmZ{kPiqrF7{>2{-$C}g(49Z&pQBWYz4R2sTL?B_z>=aKT14pVmQYPzRC_P`9f0}=}xa{*M zyS`fRd0Hy@4~j2VpXJw=@+(*>tnptSm5NUai_3TKh#>HVANU?cA1VApVSUto*N=C} z>`}NyrtJA%_Hi~h|L}e`7X%{rDtL`0%r{FOy9l@lxUfZsqHovn>2>@IcK!3WWa?>N z()E(oOTtO=i5e80Pg1j|2fc!meGZl8;^UX?CGk{QwnS#!wa SquL<;X#-0VD_x8vWAq=mLnZzI literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/FixedFormChatText.csv b/titles/sao/data/1/FixedFormChatText.csv new file mode 100644 index 0000000000000000000000000000000000000000..72e3f1b978bee16b70da6e53334a3cfe8782cfb9 GIT binary patch literal 15572 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 literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/Fragment.csv b/titles/sao/data/1/Fragment.csv new file mode 100644 index 0000000000000000000000000000000000000000..5273d23ef5103adb7c2f25d651a31b367d011fbe GIT binary patch literal 144 zcmdPbS8yvzOwUctEAdRxajhuOan8?$@OZflbqo!3lmbItk=!qA`QcJRYxXFOwaR7(eX>nP1Ol5Nh~UHNi0d#am`Bs(Y#!S1_lN? zN`}v-FExBRd*0)wHIJLNKW<+9yk+6DX)_-;tp&0lH*bF2JnM1u9w0+WN6E;*$UxuF cK;OVj!Q9Hg0EobX#y}B6AW|@b3i5IR09qt8UH||9 literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/GashaMedalBonuses.csv b/titles/sao/data/1/GashaMedalBonuses.csv new file mode 100644 index 0000000000000000000000000000000000000000..3df4b2a78799cb7baf14fce65dae0d59933248f8 GIT binary patch literal 317 zcmdPbS8z`(&Peo4O-amg%Fin;_Ds=n%uCFvtV(sv%`eR>(Q!{LK`8Mn&D9AmNh~UH zNi0d#am`Bs(Y#!SIv}WIWME{VZ(s;SN-&O@zJaBJnU$#}5GnC;8R;157{lc7DKrKu pGb2W!3D9CAm_o2^=2ivMzG0Z)@T&6%}Cd4Q-L$(^9i2y&iMMeMs literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/GashaMedalSettings.csv b/titles/sao/data/1/GashaMedalSettings.csv new file mode 100644 index 0000000000000000000000000000000000000000..b4a75667d3811de6d674eaff3e68b90867add1a9 GIT binary patch literal 2700 zcmX|>OKROf6b0u7?;#6#VD(486&MdP$_flNNg!kr@1IniZfS6?DYsfGwI1i?<=gA~ zzhCd)KR@38{P_I(`u+Fk+s7mSyZ&bF?c@2WtotKbmZgdZNWdbVAX%8;)*i{i6gx=f z#oG#!d8=&$2^Vi(%v}w5h}_jo+?Pi&7Z#9QSV6Kd*}J1*Ks9z0%!MfIYRpv>_Gs*C z?7XjGHHgt3tAQBpu?ij-nT5&T10!=GMn|m1Dn>`F24Zx?>gevp$gp=6F*2YU2S(;X zjE-21Rg8{U9e#xqBeO6?j11@}J25h#XLVv^K+o#L$Xtlg8LP31(HX0O7@e^?clTms z*y|TOF)|=lby*&!EKIS0S~0~6YQ+>As1;M}pjJ$A1+`*|8>kgi+`(a$y$7fj)72By zYGA>N+=x{fR$g4NBKIm*C04~d7pw@YMXQ1p5w$8<5mBpx6}b_s604F2VO3&PJgbTo z;o|C9Rji2USyilv=vh^)$c}s~W3;Sk+inttwW;JBtk~awAr4 zSY;Kf7OSRJ!-{Zm^Q;j++xEjQ& z$7&!}eOP4`s~)SaRmY03cOX_hRs*r>u^Na~k5$*IV@2GnRmX~mK4`^?fU93{#fpf2 z!4)eadR8k|MAT|UgNPo^iUzq64NLi2MI(hgFGeGUJa9vUTo-R>5O7n2ZZ@JHg)%mx zUt!}xMD%?rV*}=cQpPofJYIn^t|{bkl9X{xAy4E5Q^*r}ZVGuK@0&s%XGIy;6!JI? z%DASGnP^bP291GOrH}_w#x;dJ5vvsPM66QC6R}DmPsA#P%;Z6ovEe}$a;1!o=;2Vt z=0>bi$a58|6!JjIxTcVq?4^tidoAQj85_|b17&PPe+-nd5w)U>&5c;4kmo8^DddS* zrI06Ll|p7dKQ+qOc&lImhgGnG!z$RoVHND4R!ncTf?6@f4fL#-;tqONOz{Byf=uxQ z{arCZ%GiK~vuc#Fxe==rGSgM8QpgjrN+D0gDup}|s}%A?tWwBKZ-rF~c_LORWG1VW zu|dN^Zj`YR`GSoyHaB9GLT0*(RSKCYVwFOkh*b)CB33EniCCqOCt{UCW^yaa*!bKa zRw-nrt5~IwnIcvxWTuE!3YjTll|r6~RSJ0`Rw?9(Sf!9BVwFN>ax2=`VAa*2eT}FF I?Q16f2Nnh3lmGw# literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/GashaMedalShopItems.csv b/titles/sao/data/1/GashaMedalShopItems.csv new file mode 100644 index 0000000000000000000000000000000000000000..66a3bacaf006ca80a797393c701d544c739f356b GIT binary patch literal 47802 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 literal 0 HcmV?d00001 diff --git a/titles/sao/data/GashaMedalShops.csv b/titles/sao/data/1/GashaMedalShops.csv similarity index 65% rename from titles/sao/data/GashaMedalShops.csv rename to titles/sao/data/1/GashaMedalShops.csv index a4a78dcf5656b77f98263f9f031cf7f0a88de8ed..b92e0b48858fa740357a0f13a4b88373d16ab942 100644 GIT binary patch delta 8344 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 UCXyN5rC8Bj`u@vBE&uxe5AoT@5&!@I delta 6787 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 literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/GenericCampaignPeriods.csv b/titles/sao/data/1/GenericCampaignPeriods.csv new file mode 100644 index 0000000000000000000000000000000000000000..839ceb238e56254b6569141d142ca53d132a3668 GIT binary patch literal 210 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 O@zvKp|I=}rZTLUJO?j>W literal 0 HcmV?d00001 diff --git a/titles/sao/data/HeroLog.csv b/titles/sao/data/1/HeroLog.csv similarity index 94% rename from titles/sao/data/HeroLog.csv rename to titles/sao/data/1/HeroLog.csv index 6fbacaccec2ab16c51a9e6c04b9d5f485041f8f6..339cce37753320b426c20fd9d38d23910b739ecc 100644 GIT binary patch delta 3740 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 Kw^DLY$L0S#;M-LI delta 4635 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 diff --git a/titles/sao/data/1/HeroLogLevel.csv b/titles/sao/data/1/HeroLogLevel.csv new file mode 100644 index 0000000000000000000000000000000000000000..4a9e8fd7e90472b02e6013359c05c8f2f66e8c86 GIT binary patch literal 2141 zcmX|?TdJc+420he-a{53m~N@B3kXbLoGdwu=o5Bv4?&%b|td;R?X-}c+b_us$r&+}2OSb09&YAGeZo{zBV z9GtJ`qgibnV(8GV^4EIa46B>NmUCP-8n=BO^0pyI ztAP$m&LpiF=rQ^xR-Ii|G9C;hIbh|l;+(?^aEgh6|x2@}1d#30Vs>pTqQIjya zD+1yK;|l;|)JTRgu_04K$_*1B^g4~s*kVNGwX&^?^aRktrYBx%CLFY}!KJh#;|N;W z<;BB^mnUkcBKNiqgGeVKnvau85a~=CE3%hCq;hCc9Pij5(ychcJtJ+rE;1U;vzD!@ zxdKL{m*!|iUSb_DEeJ{VXl2n8G}i#+wuO=g=!#K5KeA~9i6lT{l7Rq+02Gm0X^1d; z*kj=x8`5Di#*r*!&qnnp^q&QGZ0OA0!){F06KbB z`V%9`7$mL&&Al^@J}st7xKs{3SyV!9DeUpGsf4_2+Aqc+ak>rAXV!F7YxIcjW8qMo zV#x^UH$vtSv(pt?rK&lWgYn5wD#oY;oX$Y0CXDK7&KTAdCvpqVXvSoO(wDEZ1;)|_ z5GO){!p^oHDRDB%cqX05j@6cNu_3DUR2!LmpQ zf^_<$V$|76mUs>uC-+rIP#k7IcL8E8Iq#G4Pifa3r&E&+kW%LUlzGvll&+VlUBMz_ z4`im!WM(|eSrqD?>yw<{6`ydWN0OK2a8o#HsTdwz8^D;WDo^ZE0fA zaUD5qYuP#@J!x{<*|vr%kF<}7w`7x+*VMGLZmX@G5$>UF3F1-`uNX3Q03@`sgs>t~ z>YF8&S%gAN4r?rTVxaJ2XV}i}L~E)FcGwRkl^MN7&fR816m+3^Z-Y2su_H+W<{az_xz?QExoTSwF}kad!r2Yg|huFblqw4LwBL<@0-gWd~+qX t%L2>rLXpmevJo#7@m?rv@j_R>QFh}S4MccxS&$d%cW(SL-Y8-|+#l}(hEf0k literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/HeroLogRole.csv b/titles/sao/data/1/HeroLogRole.csv new file mode 100644 index 0000000000000000000000000000000000000000..4b75e2936591297d8d6ce38afd5bb16eac34e7e4 GIT binary patch literal 98 zcmdPbSMW$J%J<1n56aI;^-R(6OUzB>_HYakoBho(q%(i-Xjd00o;TJZ@eIR<;u;0st@)EpPw; literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/HeroLogTrustRank.csv b/titles/sao/data/1/HeroLogTrustRank.csv new file mode 100644 index 0000000000000000000000000000000000000000..cfc8e0daf29016e1159affbbba1c6d1c392822d9 GIT binary patch literal 912 zcmXBSO-da=5Cq`8;2m;+0;m7G413lvU15TULcq);%R1AOg{C}mdcEaQ f4COHl<#7z<2@K^)4CN^dhX(qJBx*zXLKkkon&U2pU+;h&oM}AS? zxT5Y66JsbU2?*ARq5zc?jYPG^=`HTX9D*dms2+~3s*)htfzALO!5l=3+KciY=VZSL z9=#4Bfm-bD>6DXAG&zbn(*uA90bW!EdX=ELJ6ImHTV^pcmfO9x$nK@Em^lk|j_3_E z^!6f0qp+Ac3k|N(Dl8Vpaw1p@YaeGIuN(GZdQewaAD|%Eiy0vbhsBILp)p7aEyVTE zb13WkK*?DhsYhklk8*Kwj5c$9C@T8ZVt)S)q=t8JsED(AJ?2JS;80Ow(5V9yn|G&a zQz(*Os4pJafrkSdWN0jb>fsx-A4?-W7(Lhn|6o6ENBMC{_W*AF?;Iy_%1VO<&Czwp zH~3{gr#@i3jFm(DnEzrY(uV$lvBTQsIOW%&WpaPa8~y`~Bc8ejzob&RbR&DHU>w!V zQL&ueAO#}o<%|4Iv7p!2zT!a>IlD$b#^y2iINZubcf?%50>Q(5aRL)c7Ej?Hv5W1K zh_vu%#4ifR!I%I@rZaeKdJez2ny!E((B&$h5{>cdApC4`ATzdB37EPAc56Nwt#z~{ zl7X~z(q@|$uDETm*=q3ErsSARu-iu9TD$`b#&+Wk8G0lp6v3BJL(3!-WMS-NffKZ- zPIMqKskYnioWT7<$K|7HT(u(EIZ2NsyB5Xc^AKrQ$j+tT1NiaAwM7 z?3(%)uZ47FrXI&rN;!l+F9KPTs@M{9se!*f+C<VaW6jcSNLW@c=aWH^eBE;3 zyX9^yTfyEsfs^!&225XBgw~byC|%{2@#f-n)U8F{Y6tqRX~0iwl-evJksO7Z5Au+- z){UoYm0HNJMFaQN0ow(sNo^5Eut&MFQ zNB*okIYy)m2tn7!mGu3Z1oSN&f~HNUvHg=atoZaKj5&3RCG~n2-5{xTqz~AK5IvbH&TnP~_ z{Dvx&l(;c{w;P&0XK-PU7X^FIxzfJqsY3df&t0=id#YS>_K}}4Yrjf`c?aAWROZ3W zvIaW)I6)9hFqTE2`d|gJzG}j#avz?S`;;JQ{Go8%J#-R#zrKr2hwozHH`g)t$aO?i j{7OKQNEk%uD#oDda0C*MI&tZ!2hMLVBJ0?Bb+G0?u7t9o delta 2517 zcmYLLYfx2H6rKx&crRWgK{2_PN16?yp2t00P$V)Rsi2gFNsyZ515i;z(iESlh=G@d zW(X)^N-B;5IQ{u9=iBT1*5mBWK3!M6_W$hV z@hlH|u=L3b7ZI)>0?{x<82E*OnXQfmpDU|fE zL8*^Niggl)GWJQ952U_O@_%@^7Y`5eEi=P)GyJ-58D`1qaTam7-zmh$e%~V=>v={- zyLfc_LL1arO%Iogc@R6twjCAEZKQ7UC-tgcRC}V`^e}jIn7<9QfbG)RFqj9i&jQG! zsP{ctxSRF)U|9gv1|G+y886=JAw~s?5(W$VwE6O9IIV@j^q}(?6cPLYu~***!bRoN za`Zbd2^X_a?T|K*Iu9Il zAF+GTUs4rADkoHp3xw#wIPUsKgDn>PdHkxkMWn)c(>CcFbyyHFY|Fu&sLPVz1MV6^ z-R_DeOdt9LF=^Nx5#r_zqz=D|bd*9N_mxh*sPa7wowI|WTctPOto_Yjhzg$mVbG;_ zv3@i~fn!NdIO?{-Z?26@jhqIv(j1JW9bay2KnG{can>R&>rg#5l&yQa3vq3$yFsq$ z3R_DYX@jM)1=3JwhD6c{7!tQ0MMvUHL!BGadfAB0xE4CnG<0#p%Lz8{jyDZm%#g_A z@taVPFv>K;6G7F{+Q0-dJDgye8Qg56$}eVx4G zM@uhF6qX$s3`5^IC_2@6VYFxb#~ejL^O&0=jRutF))+fn99t*GrN!`qD#raN4Psbl z732Cq#dtFpZV>8;vo_fArfJ~j2I&*~z={d2(!|Y7(kK3e6%3!$iujP>p(OfyesZhG zuZt!oO)i&=7*WqDCL?NWo6;g`*?PHhWsyQ^chA zp9_s4>di{OhJ0eiEyVB-9wFLi5O&YFjri+KQeBz#l&e%llzsQZ4*mx5`B)O3o!M;| zSN#zwOFsTXM605%E`0#h&-so`M5mLqczHOK%%wXcY+fS^CK?ZXZW#PK4@?G+GcqM} zt#D>OovEbECTXHGlVMq8(wb$O=-i|#8<1BQoIt$4z!bTdsC^-cG8UO47Z;7nA<^X= z5^Y&*iVP-7$+bb=k^)@nUSbLj5=Oz@rF0AyeR4t;XMksMIHcs3u_0k^;K`2!|7DFb zz>S^}@Nik98DLox0quFFLmW$b(a|peCakQJB=69% zs#y|LeFQuUu|q)qN;D|SZ;(q=L^x!w_8|RywMp~TU29H?+BC>Phf`~5EGyRiDb$9_ z)EQIluyg$(N%B{lx#6TFc#$aud&N67@ZwhshQi>Ddt`_U)~<_$CmXBKzj)IHku?Tr zBC9u3alH#~B0emntL?@XS!k@nge^q}xRWX-Qnd9P#-)7Tf#@uz%d)eWzf40>bhuR< z4oxNW4KZaKEm}Pn5wCgb5Z9H~BN}i7@itJym1Ts}wznV#?VzK5eFuJd)bIMj2QM(# zz4IS@sQi*1>Z{AyrM7n${Xk6xDKjeR5^`3tGOFmCrE+&WV&)zyFSeQ=*jPnT+>lru z1d)5cVUvnOMuAf`QE+KrFvNTMLNNX|?9cL3*-~UQG}Qc#78UzC5p%yHoV@=AV$=ab T-&#uCR(k=_^L3jyetG-{s2mkl diff --git a/titles/sao/data/1/ItemType.csv b/titles/sao/data/1/ItemType.csv new file mode 100644 index 0000000000000000000000000000000000000000..ada5092d958d625994a7289be675078eb5ff3b04 GIT binary patch literal 412 zcmdPbSMV%J%?+t6NcBw7fie9Ob5nV_40V(qH&1%pyytQAS}57H;c@eHFn8kP=C;R8 zi=XVC@nlDz5-*pLj?%NW%bw0$flGn0j?&8+o1V6A$ECmoWF3m(FP1NUHf0~sa8nGa zXWKfS_U#5rnE|Dmc0O(bx^Oj!0di{dh|^mEJaQ+na(W literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/LinkedSiteRegCampaigns.csv b/titles/sao/data/1/LinkedSiteRegCampaigns.csv new file mode 100644 index 0000000000000000000000000000000000000000..745a86d3c34fc5515a6e4e334d2305c0e581507d GIT binary patch literal 269 zcmdPbSMbTq%T7%R&MZj{N=XFOwaR7(Fw^c$w}4mFG$S`E=epZaY-zJ@Lcm! z!0dqh;*!j~bc7^S04DF3T3!q^#v?VS07yBeI3?!gr51Un@NyX%7#JApD1~JPholxg zZeH}bdBWqSC6Aj|Kixa|+4QB)XYPL4v*~fu+Q-d{A2)A)+&t@X^By2WNk_@Zz{pVF n&`96FP{G{FzyOHAg2o0w5s;vfv6ZPM5Rs-7Xo!K10WTK-&7oY2 literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/MenuDisplayEnemy.csv b/titles/sao/data/1/MenuDisplayEnemy.csv new file mode 100644 index 0000000000000000000000000000000000000000..50db0f5927c10542f66ea9f0b48d835a24eb1fed GIT binary patch literal 18768 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 literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/Mission.csv b/titles/sao/data/1/Mission.csv new file mode 100644 index 0000000000000000000000000000000000000000..bacb114948184a96f67dc314b674ecda600ef467 GIT binary patch literal 3747 zcmai$O;6iE5QguS`X4IJme`$LC-E^2sT%QhNCdqhRDxuQ1gV4r|Gw{d6YoxD*S-XB z=3(ZYFVE%WkK6X+qkZ46UaI47SMBR-`}gzh@D-okJ@2;Frrm9C+Fg677VrC)_5i9{ z?zg+I_4WYMhv&D?tqngYeDS+|*?#|Kt+AD@E;h?Qo2T2=dcC^8d%9lT+&md$FDf$l zva>OjsW$&U141Q)B0>NX#FY>i5hoE}LVQHLMCuY!N2Hd>q=ZZ&GLgu%giIqcmB_4w z%px*N5kg%kUzg^DuuYO=GE_jUiHN8R8COF3>a&gT;!8+heKMjhWa<*qSD$T!mq`if zt4~JMh0L^s^wnn@;bm4r`s$Mrb+JupUK(V~g?WKZ;>9+ld1;Wb6C&zjo6@{A$k+)H zb+JupUK(WVgrs#LhyHPAJ+`>N{dsRg!q84^V!1M5_VG83BakZ)S3p8nc3vPiW(;0@ z0YPAp(jky5ywn9G@q+b`c=6EYhvlcvC;C?e&1jL#?n`H2<}`J;%G?=e1Egdjg; zj7_l5V=2+4#p?TzWN!8(Fo#YaMTC534UdF;ztH3SVcCU+&fpgiKDhluk2$<=Mi9%0 zO_AnKBe*r@`n+Y zPpN!|z|KqQLS3K|b?KHvH|`D{d@KzLH9~Q0NHl*$3gHd=;_wh3fAo|`gy$_SBIMz8 zJdyZ_6e1j^I6}`6`WT@{gu@g^=s7~)N9c3=^J@8PdADBj@q`A$9J5gl&e_^cKxfc%4g1FVYN!H@c+sF3nKbrZeulV}!YNs;IC~1*JD?hQd;jzVuSf zP*|&y(t9;SVY5m~uhtBOY}8R#ox7{&o6UT2eQb@o3N~F~eK>1KHXk5;I%`NyA|QP{Ybd1rG`@iJ`K+Ok@)PtQ1`Cnb literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/MissionClearText.csv b/titles/sao/data/1/MissionClearText.csv new file mode 100644 index 0000000000000000000000000000000000000000..316e1eeff9e5b73e374acfcdd735b25bce95a3e6 GIT binary patch literal 1716 zcmbVM%Wl(96x<{64=C$MjqL>JC$Q%e;15`^tnU@{0fjsZRROgmDQck)QQ9<$fsZ*! zDpvjl=FGKK6h9&%MV6(^+%sopu2)v>J$h1o{Pg*=yLUXV=2a_V*TwY3z7caN=1}a3 zn1R?$G3_{dKmFO6t+gfGi=(hYil0!%VkcrZ#C{bUiEZh#fD(XWF2sHyc$35?xkn|e zy0VT5I8$K;;A1hLODGMcjC6`QDS_SB6%NwHt(Y^hpTyz?++n)WKG$i*Q9EnG13h7v zMq#f>qSIn2SOJk&d@1zsq0-mIVi(YN)N?%5J&bEEj`JClZUi?j#GKPWm}%8RWp%O? z#)OlB3B@8`2t!^=SelgmMk_8!fk+UkvtSTof+T3`F(W91q>O2Pjp@J{*oEJKwu1$Q zs06rQ65o`*lii4^?NJX~q3ggC7avZ5PUN0Hnt8#EBSr?((B79~U<=e@Usv56%}nYe z4`)}MhbW;AYAuTNR(YX%=^z&t2H`?EZP^R8%A`=0p?2F`(-0SH4wzR7g6UgQSD48a zP)J=6!g7d=X_PuVCT!7U6s=aRxDxWh>Ye=)cIO*S3Ezp?b{_%25G7$q4VWUB&(ol% zSqcjX>nwxS3F|I{H3;jK!ve3CY8%DTK^$Gh(eUQ&FEOvhyh;lXmwvl$%um11Coldr zA@JP^ko})0)Ttq@it8l&?6U^iE(r(I$#8nTH9N(>TM}+Vo{ON7aMvggpGWlhmV2}R pW7Y@$HnIlQip|I4wqllqyAqBiJj|g7x@KGds80Z-AavABELRrQ-MnD!=&_u@wCJF#v=NR1p literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/MissionTable.csv b/titles/sao/data/1/MissionTable.csv new file mode 100644 index 0000000000000000000000000000000000000000..b399f22b1d63cb8ca93745daf65027b6522e2ea0 GIT binary patch literal 830 zcmYk4!A=7)3`Fmd@(+B#N{#K!w)+VjxBx%UiXJKOJnR$u#aCmyT z-S4;WulMui^ZNX;{FiUPm%lgYUe7<*-fY3o2L5{U>`{7)>`{7Kvq$M|W{=X_mOV;u z$LvvhJ7tg3+s@w8-$n`rn38=c!4&O75vFV($}oldP>3nrhf+-OJ``ij_oy5KKx7UH zAe7@IfKZN;075xV0$=MQNB|)NCjmqnkN_eLNC1%rBnTTwz)g}MaIX?12wVb1kRXf& zf&^h}3KE2;L69Ik4T1#WWk7<6T1J9Es@y~X2MH0ZwM+zSE@RpfYc884m^J@N0tg*@ zk^n--o+NE@yLCgRZ4Aq0M`nX@Bjb+ literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/MonsterAction.csv b/titles/sao/data/1/MonsterAction.csv new file mode 100644 index 0000000000000000000000000000000000000000..052d917d6da43ea4b992a4f45b51a1c252943193 GIT binary patch literal 738612 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


    7A=r5EK$7_{rc1oFRTYsP(2%0(NC#RlMV z+2qPKeU} zQ{nw=F@yNBPr zmU=CTv63jknb(-pSaQNbVxjb$DsllH<;j((T00%d6OA4Lp7b7Hc+cOnG!a&}Vhqo# zbSaF8(j&9Mmsuh`G`hX=h4i|q^iI5=oN`-YrRndg+~h;2?F~V2+TO6O1DfKEjL&sT zI%JM6UzRPA(g=FZTQ5v%W|k-`RlJ~mUNDzK)&8f5F4gD)I<3)db64sty0R@gx0FA= z{O!ZLW;UeSWJoP3GDKQDUL2S&73%_0?S0sWZ?xouwe5}XQqgP#T*F)lZUxJ)xQJFB z_6n?bM*FqZI^DL8UCtk`c1Cw#4bvcetB7gm7O>Qr5#*&sx^11XoCSvNXtN)}jNrsi zE2)_;(mBoKzDqD6#=@yCc_QJmvPolGM=9r_A*|?db2w9+mL~=RMt+M0b!ab2Y&^kd zUO>dBY4L4Ly)IC-RoiyFucd!BmYc<>oF{_gSCN|kN_^tY&{^Twudyu>p0^WdhW5wV zlI0qjh1@-^lbd1|T>X8*-_g)4=wj6QqeR{t$SgBhhprR?zSE&9VEMy*?!#P?5Zu_W%aIUo;?}cI&(RX2m4_n9z=qU zXGrrMJgE;OoEiCDrz_|2V1H%Av;NN!(2(FefNEZ6%ks8PQO*LwgY%g@|MqXo^BM-1 zZ8$l*?ebyE#kP({&cg{@4mk0+z-Lb2!J`?ev-eJCK4S0mHs>AZ{2A;riP9-fD|Jbq z=6ZR5wBw;y55v8Ht&t}^Ja|pB!0)Ty_bP&uc*n*=_nkAVeR-}9+2)MntdL~JaSEz9 zxgl|PK-HAZzU^&}HO_*PEhke@<@wFk;?A2+0x0_DoL`&;wbKAqp5Gbh8}s^=s7T)& zT%1L<>wGXt1LPs|KCl+vM-K?*4dY1t|!Q1UaFL3;GoImybU0aOs?hU zlSAqOH%Ir61aPEyolfk4gBPAwh!NbHfD>46BN0)8Gw(bCir)j5xacitiRc3~dgt7F z+CDsC_niAiqbSd=qV!;q^Utz;F03Fg0(Fq3?N>FD;`#|n4E@u?#rSChrXQPL5mNaxR#UF zxoEWwL=rHl^PuL2GChJ7I0y&}E(j?Zq9K{*^blI#@hjZ9jD8es2gMH>od4#1!sUqVE8L^sRG*9T2)Q9_g~9i?ZlDNMh)2=d+QlyVekb zFp|?9aFCvLPOyVR2F$NKcXajhA89Qa`|;TH&kHp}#+?6MMnL+`s&aek&Sz#|oNQnCYYdkSx0 z)W^w@EV~xCnwc#{!hy*SIPtI5P-qSC;k}C(xkcXQ;kR#!=b=1R5>$FP(Jy0Qg{Qzs z;>pSvh)!nB(RBF4EVMJm000~a{#AUTC38y4G zkZOl|@4x=)m;d(jzzB7YWL`eC|I88cZ3IhQOM=A6gbLPbFcovpgQ-1cOO0?EvLhLA z_N!Rh{)}v#ZTQYe@1EAaBKJ`p5!sp(kR2dk>k5cm^=F@phg3PsK`!Pir;ELk!G>_L zb3!C&Gl`qykR4obmz7~O%V5R~3EfjmC1pOsr<0fSydxS#;Qu0i;b1D}cyFhe^yrn3 z;T0Z#i%WD%#33DfO8gTTg-rz$t3}P;saw<>mP)_gDu$(bz_E))deMB2KD+IWAka`& zrJ{IdI3xVF%BeIK1d!=^1(5WM@{nT}B-)|gcpL9pxWi~*;>Du0A{tiyFjpl`h%~Pf8`5Pp^!ouWL~*oA{z_NR{@z4HZ%%ZC>}GA1-c@rG510u#b(IV263?2@T-edka*v)h=qp2oIS?7jY6 zo)fx&E-niQ`F5 z+2m?8;1=#&@{uC6pAnj_(in*lodTtvDQnrG7SXJpXE@ZLXfV2gHglUmz(ap1PdRoa zwF|1SLp+ooVElfQ>#Ks4EVK>h7hw4zQeN)p<`tI#7X#Zo=h($1;cZ>JH8E9w5|Now z$?TUHn%1*!IlgsB7T@ME$37&ymsY-Q6htIGP=9b2XarRgQF508BBmenJYgTvAiy+H z;bskR<+Dp^Q9ryKU|(mYq#$FFBHuN&`+H6r{~lP9bSPNz%C9h0hJUiQ*>9et3{w3KSVAi7W z@rUQ1zlJt+Hkd^ zaPyF518!oZ0GEu!LB=z}y*`24-iNNxW$jnV3Qg#^chf{?b`Aq}@)QRh&xrj-!roet zu8<|$Q$miqkuF}#olSmXKx{mwt>I@x+~1h4aJMSB4GF_-N{L&ytJ!%Bcv<@3VB{I` zc2=k>*jU<*F!{ZdCK!Z@=_+hQb}R!{I+ij#slCN#q2kU)b;TO1C=ILt3b+bVU%-M> zmF!0LHUn6CoA`g69W2)DT!xp>*s!j6@$}<>(~{mMl5jQ1j;NzGM^vXYrEU8xq6ONt zD7Rj@u7H!HO5uh}=Gp*bOdH3BZWnQ`%LLH$Rdu%W2Y|j}1W_+xCup;AJDlqO_O=tJS-eV}sW1s4|Cw@~dq~P;5TMfH$(q?8 zB4ORl<<0R)sz_w^?FXzKi=4@RxKEs4qzFLO+Tq=T9{?0rn{_E?ZDSQyJP`489d~RD!?!U0~Qurh-lXXI+t(m^>z8OinSxttY=mPz#XoN zSN%oX?NliuL0Hm;nf7(l@vH z2K`k-vBOQTX9p{byZQ;MB;*(<1zyu{`w6Su1{_eU&Da6IoK<`A?-ByOFSw0mHi^s|~Us;h0zQX$!UeoT03#E6dzGDbdZukn0piHZ#iRD!8Sm7m_Sk~Ub z{{aWv27!Y1K3r`vKM1r=&i^T(DY~oc!xIkxQn5GBbuK;(o~OeLXf!n3(E-~In)SU$ z4^SjVrT-nBn>J(tQ!Ki~>uCzrPYdK!X8>epq#k}-Jg8kic=7ng*GHeorG!AQD zyq#7oUaNS5jEs!8QcE8sybf42j@#i<(l!>1>;>T*-}&?LtuK$S-#)tkAjqe( z5D}A|7;hU+S9K}tPl_c1D4GZzpY5C&=JqZ=hu3ieghi!RN#Ch9LzOdv6e0u!q@yH>GL+Pp%fQ4vsiPUsf^WE z!vzoEp5pxm8oA!MBM^rMycdyG%~_G|zCYQx)Kmj<~V31FyqJ z;I>TJsSMmhRWcIjD}YL7BY+|$K@qz8j>l2(zR=NEe@ySLI20qK4wU3)iFRqxTngv; z{8FG0u#l3tjsZr* zvZdA?)bJ#(%I=f5lj4sggponog#tU(pc=Xlr)Z<}PBExv=SvpT5!;T?Kw_;)HFdKG zmjWlJ^%}XUizn4~bgH%kWNuPzg|5}fNfxpJ4M?~mODy$kKE*_4;S}j_>g;R>O+BfG z7<1(G&lb^d)rWUdNdh37+r&ChCH0BIxR5CmkeOY!uWqJzF^BI*?@K4;go`d zH3W$08Xl*jrO{wPaz{pJm-4~#$u__p(s&kau0jT|1(O?CQu%Ps_LQS-fLQsc;dY#L zv~6cNe_M~X0meAF08!z$`7V=VEofdxPdM7PGaA1gjJ5$xMe9K(&Z6>NV$$wt+YauH z#+@xM4og$ZFrqQq2H&zuAdoKdCHTMk&LX2IGVFvgTFl)bpwuy!2^!Y-AYx#ASI>>WP*07 zl#n@4FgSZZvLQdGtAz?teYqO^jDny9Oq{6wvlbiC755t*Q$bS;=WOJ4inKBU<%wOi zpW}0e_lX@g_wj?&H}xgzf`uMx?ZVJMNb zjb$b%NHoZU{k1kbjpcDQpcv_DE)8o~NZBRoP4AaXaL{km@!5H4TnjiVH4S&h zjs<@uIj=z;rlk`cq!^u^?c)%SI=uuDpj7Tc^j#$-qoJ~7{ibthmeiVHp}(jTvtuj= z*8_^mTL=Q-wY8Z`ITwJ*2V>5{1O(ZHgR`7s#FLV^xVWtNuV2*HW=|8c7@CE`w(d=U zW9OsmdDHv+;e^t?K~RO+mh#?kc6J#cRvI5&{6wT=u&PTG&#J^FKC_%y7F4=PB`&*3 zpxrd?*%j1Oe+<(Ss!NLO?ywVGAtqB~EjG@GNdrVxAplp{aGf(&30j1o+1Q0byhhpadp;D9pVGcGtl8#Gf;3^G|7ms;6 z16OVyO0}yAI1;q3{hR}bN8Dfj-Q(|G#oPCscMFGDp^Ar?bSo)&VMWueD-V%suH&(N zSWJPCBuzSF&sdCTndj#8v z%lwYI#`!HxBUBS~b`gZA#@9(2+_2S9-gU5flqumYpXe)m!FkA#F zm!dG>$=xbp($nD>?_YGcn(TZ2>EHgBy`w+AeS97Zk}e$X<16|E?qY4rXZ+Lu4Yv$I z(;6Zn5dFX)Bt#$}aVX-XY%);z*OWu2I~?Jiph0t~?w?>&A*UV#h#86n^~2FfMk#8R zF>FM^tH^oX2@2$JM<^;zh(u3MxC0|+AY2%{bDY4PzC#Q;;nta6GX9!oHO=#TvIR{Gmt2&^nKyW#zoHuq!u{y}n))Ew| zIcQTbGPr@7{PQQbzU{-R637u&B1z>Zur~NKgf|0uKZF| z=46G-XJtb0EF!@Y)yV^WZD$`XdoRRANB(q@kyKqL47M%5g=(RNfOiKW`R@@CX`BP8 zeMC6O>cvfxQ(YpAaKxg7ccmt9YC4AxEy+NRnhxN+@v$S;v4&8Strh7wR9Qke z`s1@lmoL&<`tweSH7afm_aakkR);_uUZcx$u%SMVOZGvC@0$Hz5R%epB+vMLx(fVf zx)6Cp&y|tYxLvr)6^BL%a1)6A^a!cW^5M1D%ZDYgvAX{7!+ks$_<_a%-ysKD{_y7V z#veF--}&Kdeq_N7<&XK#2Y460v10tp_7BES4WWH3)i07)U%4wAP&( z$i}(!!fU=8_#-(IVeg55`J>qUyfkakC~Y>Z#(dVI7y{ds370Xt95U!=BrRILMMC96 zS)D$Mu?r`522v|D4v?OFN@JI!g@rav4)K)vjYgw$yml5koHD<$%elf4%+_i4!mUP9 zb~(n{c}AXA?}Oxv%Xn)d%GUArF%E6XSdO(xLDgF^YISfR$AxK-WlhlQKR#6&WLH;y zjwBdE)|s40Zj)9n353Fv&hl#_tj!P}uu}QyGb1DURau*Ef@H3JI%NWz2y63zSF03Z zZ5}EuT%Iji*hQ^j#lmdKuGDMi?n#BPn>qnao6p}q=p1#x%AzMm{%$h%sR|ks6*|MW z3r~IwabX?>;3_i`RPl^?lwD3h4zw`B>m^kHtg*#Yw{LV4<0R(qq{*zNcw3VfO!&W9Se>uv%~4df!0j%6vBBDX`X~f%a^;l5OgFT zzvh#i3&b-+$!AIXa8Pj|AHmbi0RhjDm^+$NGqUA?IH@>5GBsIsLI{wC)ZBqobFw9b zIIegGNS=xSq#-$XAk}WUWrsMtIDj-v6#-4fgY&n&pDNb8zwh_`gox{fq(LKmO|1 zW+c|p;35`ckd`rrTHA`x{RF-6;q6FC)pMKFx-?;G3%r&kcR4IrV_^G_03iJlKy;d; zmhk)R(rca>>0=;9%$?pW%DHyOptZeNn1B)!&1(qaMApcrH%Pi=~m>%K8-$G=M4_?G`SOyOIk-{;nmE45-!(qQ97*A(@M;D}uZbkRI2w35c)D9##0On*cw=@S!>h@i zjV8O?##zH_&Y_!`8CE!NSNZb9WD-T0{a@lM?Jo zijx~M$Ud|m1*w}mHES8fJJhl%d(r-<@o>r_AmSgC&Jf-LDYT^KZa!nQ%iWp_p#`f_ zLNodvLreD@jRqySn!eeQvI}3L>7F#@77R-X&ggp{oMb*G*t}6CpD7zvcDZ_Uq2Nl> zQoxf-<;jQYSSQwH3NN<d=WmUGclsFG(t%ei5_XZpZ2h*<<8?V)*k3SOcPNqMOKEL?* zv#(A*dim%!9L;YYy>RRJ%E9ra7yjzx)7MVUze7bjJbykJO+4j)=NbGJ7;#A(tfQB# z@*x7SdJrp&VVOeVU$b`(%(WhK2Re8myW-x}?Q!s4uutwb25{TJ*5oZqyde9Z6xZ6h z4d99%aeze4O3sB?BGGgCnk1Go8W>KMX{QM=4Cv|-yJf)E_jGah7bh349zFY2KqvUt zf2_Ft3$dBd@LYcroiWt}9n&40!1!9y%n~?Y>L0ILUuw&2C0dW?krmVyM5bXuIrmCH z28*>XNDk5hveqE(IlP1biN%)?b~%Kx3=_`r?kk5EzI*)OKEAFUfBu*J!1}p!#8@3` zINLnmy=@S~2%7~t_}4^;w38gkSO#&uP&HzTach7RRZV(h&izDJLIjdXF>6A#!8>4w zskF5W)yCe*^9SK-nyeNGE2x>@%`|H;!Nqt-$1-HcHl86zR`EA_G5;c}wtufR1e>!; z-`{^nlnt3H%%ekrJfiMg0frvxDu$CZR}BB2>e-E5cyYL%OR?cmH{IL$@Y}0L-(uSO z9n81{gq8H-bw~^%4_S#r=EBSTQ39l@>8!>AfO)Jm%WK5535a^ z-Z_-t@_2iFe)?L9zS@FkTk5IXD3Tm7Cf*7%G0#R{ZJCVf@9Koc42UawE@&JFi0WLz zolBAs2trcsT=}u=#@7PuSd`Ysa=02G#C*n7c@qR}C6IxYY*@?&+_0PVsX#rD8^X@0XOI;VT(ae*C7Q>eMyee~Jo0IJHyG!$w}>|Q*~ zq|qfHQzxPB1rozC0`>I(W&W1i`vns;>5`Xdh5qzg5 z0ueVrT&KXyrAVUV1Q7Up_$*y?hWYES_xPz8>%a68HY4OQLpYb;-+%r1#)p8jiFd{X z{a9&`a+Rzr&>N!q#^GO-y%JFwz|jj|!NC(3)@g|U=@N3Svb5pytMx>5g>jr%9AV6% zr(+aD3ye}UsztV0V#FKeOEFf*T3o&nMmyOkM=2-)BM7#Eachs6n0Tz3xt^SCrS1_C zjQON@$HUR|+*Cwl+OA|!o$PiCkwl@dKYss(plel1G9uz0C*TU&7BJ-q)K60Kl8}<} zL4^q~S4v_Qi6Z3p-=_t}pH#Xvj6!pa|kb2~8WpKQU zFhF&NU^K6Q4fj!k(G9GvY3dQ4B96K1)NcTxsq?x2)#1VKDsc_naT6CxlsRg5aV38| zXqDEGfKOUi>$|9nF+8`P@WD10_(~PS&pL5^3mt=ciRQNf<~(FRPaR7JKp68DK=`(eu%mCQxUvj}v5SsZ8QD|ZYfvL^;@^-Q0hIGbAYFxYkh;$&yYxwhU z9qVS=h>WflfaSA<)0Nz<^rTP3Me^6NhsI&b3-fTffa{KG{w+k&5lhAK4L?CdM0&_L zYJ%6%WXK0#TW7Yp_h0_~7yt2#U;g^9e)TVZ_0RwMi@*BYUp)Qme~m~gCJ#tV!)XPXX zrNxGYB)ToBkQ6|KZ;27Mo5UF&E!y2-5LW8Sqk$?-wOGO!tmWnpyF}Wrhd2$%}Lr^lv21<+t zBF@=CLilM$#9TQ@K$lL;XPq6C6o>$-P0V<77)kIRkRcxfWq0b_i$RWqWULbe^`*jl z!`ckh7RcE1CJewyrxXQZ0b5{St@%J8ZPy!b4IAtI^uIs-Z@-MkV!RufKYmZfO<(hg z)U>N59RO(4E*_xMG)kY&tifk{$I>L}w1{x*ifaa(F<9|Rzu-}|o#@8$Q{l&xcm;aw%t(`kO z|7yTuH>{S*NH!^lw7KbM>~<+HS7_+ah+$9$!vBse>H7qP*2bv_e#?pOvB-2DKegx*U*#~>WkxQ)v@9;f2E;*>}38H1h4DWh+n>+w@0g;$>XrK@f%8Cas%8Q4g+f&i(OwizG@7HLN zH;@)ticBcc$N`4kS2dPcA6llmVhuH6Z)ij8Z12LEcD zu9apl%pm-nns|#yb&6pQ*C+aL5AWD&YUC>_ZlY47I+a$<$lBtFI=N;wf>r%{xblB^ z@bKQfBRuNx##fNm$@?EY`rX?qr`5!2A<7;cV7~de_8*Lv2dS=4WM>_7Cd+HDJ=5(Zbu^@H9ekKY^G`%G;q5fqsV5bVYobFnnw&gzH1w!- z*kKmU-x1A(v(s#+qY03*LK_(G{6T;^Wg?4Np=_lm{r9$(4n=b)5&STCsn@0HhMb9^ zD={Uj348ybeK{^5I?+1TPzeZ7z=*ITFlor*BML&aMwfmy;q26_*pw0tKP(CAZ}KaK z_BCN!?5gt&5t77YN3Z<;=*H`i$3Ok*>3=V+#@tY+51EQHkKc8W>QbSu?9x>pO@)l6)g~AO%a;#w9fVPyhDc|FsFM za+{){vay4SlUxMUD*~(irggiSQ#YLF=%V@?K>C}ffBt{BM77Lo0uG6n@DGn^*XtXJ zKN3j^pS05>&oL2!!16n#P;m+VB_=GJTX34f&6 zG?g7raVo5lURC~6#1l9BmI0j)IlHbi*yY}&0h*yU4X>SOll-!yiznxAVlsx6DdFT;N}rf`kDx8l8KDZ;sKE6v?~EEOGZj6QsKm7ir*(F{AUJKx=slYy(b-8 zsOfxhJ`%(GBN62xsQG+k!o|o}2N{;)8m1&?Uc~Fjtc-yZHab)~$WUQBRaSED2|Ri+ zAkHOO$%Kx)u2T%N=mv?&#%7WQN>ndAoB zsY(Dc*7oozUZ+!}Dib0y_qJ9ihlsk_N+4QB+wwG9e;a zZ%^HCAUajf63{CDyH;;d^@5ybf<~s^o>nGX(E4dh0IU4(Ca?u*%LI&ktviqkzOK`R zz04(0E%U(_WG)jbayNdA33u11ddW+m8am&Zq?)LjG;aAz-HkMn)uQ|*AXWZ%JiZjx zEy!OcIg!kDP0|$6@)Ra;RgQQI*OC;b0kbS7hxH<3EzM%W%N%;*`S>(|8e}mOAdx(%SV5=o6AfB zBAx3@UWMnXM7_(?nSfStdw|w z$h0_6%5OHSn50L4m=kZOl3p{h2y{;(v6#S-Q#t>(2;3;Q2y7L%&L8X~w`j;Ly~v@s zfUKRy>!yJa9#@wkLYm3qmFdLFiD)I&@i**~@AXKGb}c_@NqvV&1p;p=tO% ze0JrXlnM|{S~9lshM)9w#*bCMWQ87#*3Y_OGxzalu3V4ubCap!UEsWV{(SR6;^%8n zxzZuUeSL3@v$r~6q)$-r4{xC^|M2ef55Ils(MO+if)Nd>Yv(p`Od5JmPCtUmz07f$q;8lX%=pho}v z&*(WmJa|R#1l|F_Q0JB3o}53gZMJeBBoTAYA6<&l;P+8I+#5@mVUw4^t|I;vm64T9 z95z1q;s+-;Uy~PIKIAkXKYn;Ko?0ObEB}duP2uOa+c805KR!saMxmLb>*2>Y(bqqC z`tg&V8fAxx?A(i>gpg!l_XG@Ce>w~8;nVGooaNKAn_hgn-4Yb?>QC3MU464~A4T=| z|Ic40j)X5PjZx@ryYT6wR~{Vh<2kw)PHz4t6+?Ma;;gLN52BF4;C3?MfebQ^4gWRY z=j^N$VAWY!!CKFQ{m=jN*M?SH&jn~J_a$7Zj!tK%|5kx> z&IXN4KrNG+1{|GLmya&pcHkJZ)@4y8q1S|eEqA$J9LFCaR9B=ypISNCByW7an9ivc-0$P$*^Jz zuXgze*jEdrNM{sC;~e(M3!fccc;@lfe?HuQzQjIsn%WBOjmQx+bNnl0Fk7H=Ilab> z4z2=sn!~?~cZvJBal1zrH|5&oXrfMgNua<^10!>g4T0|`oj$tLl9`xM6CI zUawNE-@NnarDyoaQYr04l><$=K=lus0*h5Exvc`cV_f{XONGk5V&;@RqGbBu#xS`xm9)}0$%0QPL zTagob>G&`ipiuUF1T68G6$evUPeT^O!A>m(Zmj4}_u%Mh`BT;H5LT zfPVDsM|g>dUgZS+FqEf##7)3O{fWHp5^SS4R~K@pZIr*)0w4878)AiHK#p1HVkHaH zQ;nE6e~QX#M$bfERu_8AdyUt^>w{>$wzOgKW6*la*k|j0Pt#tq(|aZ3r4=M1!dP-^=GK4=ZJ_v@YZ5M)7b-IHK-xRJDAA@4IFOo7Qjy2>8XSjlUzDzG#(Z4weL9j$_5WSslUf{>a43F{#%8Q<)| zlP4Rd@Ya6x=&j#9dMi(K8#p1$vw>@}rM~>Sv65NN3LLr?F!zdiX^0JR2fmk(&j<@d zhxZ!{RK=8m!IKZ5PrJc}iu0Jyvq_iiu3pJtXN8HMzJ9KFGs)tH3!gu{ck$@i=OVH( zb%F-ggcH=E1vF2wcm*WtJ1Zb78TITTLy-%E7UB+0O-lt%0!2?jsmOYAn-m4tnL^8Mfv0Dx^pR0 z@$n$YJKr4LdGPqgt-}j1=LcL9h#@mdx@JuxWEVm}ksR@Wg48I6W+H^$_F+g0IUzA> zq$Gk)*lFM_G)v3Vfb5)t^7wc_!f0JbDa+#kH>*7O)+n!5KoOnd3JR|^Oaw#%*B)ZQ zaVMQfK5+m+zn~!82w} zp@hT1M@+dZ3Z=X(@!i#&YY9L|q1*s9k;xkDAv!)J&l%m&cswb663(ZPO7gBuD&ndq zP08t#0ERv&w?36pDT(ytyxP1(|qc>jweZ=F4 zG#Y<;elJE(TqouPL^IqS$eV1!=aWW3yiTJ@NkLzDVzT=D=vUUC&xaBv6VybI9L#`# z%*BOB|rgXrd2l*y{7BIV+xu>qEnuA#i4LC5VF7jH``G01TpQ07g|o1Q9^3 z5QPkK0PPSgdgel*;xzDd7W3~2Ez&bX@R=``rfeo!g+;~h9oCfOf&_b#gVlhBEWrgs zC7N~FZFF?`a_Q=g+x9X=X^K~L)bD4WLJpr!e&7zF^3#PLUV8k^`{7d5ZF%t|Xh?<5 zZL-(dF^8YwlgSLI4>>bfY=Y;q4&Ihj_`!vv%Xq-|C0%uJ=kUVKh$ZqwxD)y+9e?xcqmS?DkG+jhM_wM8MqN<}pF&a$zY(MgEh6Ix z`U=rb3cOb6#Fl}SmE`)P@=g-HtzGTHIU>e;UD;B`D zqxwLRLc+s?5eH@WM`n>mWeF zJo&x>M3wews)?4B*JT1Kv=wE;?sA}E*CDkI3cR_@+boNkTK1JHQ``H%Yj(eZM$QMf zJi_wOB*U`MMCa9duq5Y$_B)9Tc)!Q^#KM)0`~^NW|1>?IA8bL%6zBnq!z26XO?0(vi-x_26~*h~|PP3jP~%cF9Qi%K=o% zrn|l)%#l2qjAjvg=0*1HP>N-fauTYAX^U<9GfEevV*Eznt~D} z+z?)5tBGWdoUd~sHQ->XM5oqzIN;qBL8#%VD7wG_3=Bezn+hD+#rgkfd$%4vuj@?k zuLQYLfR<79)qz!4jh;yc(?Dkupff=33bNbNJwQ(~=t&PScVscLNu9-`XvsQIKFC%q z)0V9hDe1pVv5MrC|3W`&J!|c~*1Pw+-?z#!$k1PCn%~2<*ZEirV5sYtTQmc3&vY% zEFxfnCkFkX>sOH>^7X4!Hk?N5fPtd<<&|fbe@pj*2o_u&7*27#+?b2cQ>alCO9ep1 z!l|C<-${1gMMRUPV z8{$NPZn5y4;Ohu!CNXpMxhfb6^no~nJ>k(z>^$2>zo&hu=Z9=t9G?n?jt^(KI+S4w zG^QNG6D_1+g=wD0|JhoHEJD@WDOk+gfzDK>`~2Q8irkU@AqZR5*^+gMsKp6L5{9U2<9m znzkllD-P-w4qx?fAR9VVwWeJf+2L;tnYQ{Z6%Mrv2d;W>_B+bf$J*yG476?aQYr*` zDV(e7LD0iG=vA}!eYX!6Y=Mul z&!8C@gPKvUaHxMdNY!~bq?M6C!r}8`B2WD7p8WVSx?7^^8ONS3@@A%l1MAb(Oml(8 zO!J0=Ry}B19h)~);^z)nd?@r~8)tQ}@LN#H8;)T0;J`Yxmm|#A^Q?DxG=Ai-I`Sf> ziV06O;?>)$dGAUaRm>X>X7#{y&qp{I^ZRDNn5pVRY5hL-W^H&#t_KCj+xn);6XG*C z2BxZJ#4ANqCu6v3M$qL@!ze~Ek2rdLLF?#8S3OAmP1EA7%g3DbhKJaCWIx5dDP1f4r%Tj+lRu9)lI_=UC^TsNdsQ>UtAL}F@v4ca*L1HE=cNis_(Zfm7i z$$DpKR0pC@ZFf|UWH(da*bKCBOenKGqLIauWvtPO2{SZx-8Pc`96-Be4pWVMj8bh_ z7~ZoqTBu3UvBqFhN?4fjP)|tL&;ZX}Ys)#g^3k*RF`a|K5+oR2KDqL177n24#qb%( z$o!2mdtQ=Y^1~|zAyRba&j{6J*+{8lfbgv)gs*=Z5Vk>EAR`z}A$9_=#gf2DcSHfl>)*kEc>2_c1_Q2e_sQ)C&Ax-c90a|1YU{^8LZ&}kOuOZ%(# z=Ak|BoqD0i#S`B;@f-p~<2rR;n2M+}P?FRMAp^^el+wlsmzA1_cMY5IH;P0QtyWb> ziSIHtx-~MRG{&}0u`Rk56U`P^4BBnR>Hsx&$OG= zuXtHx2HvcK>x6|Zuc;JJ-&U8oM1>g<^&13w0nzOvcBMD+*$E9JB9`V4Sd-P1YG8l& zgEAzIRW~vZG$zuW7lMQa?KO@Fhw|vIPX@IAg8K3@;In{8f-%z>;4z-`jmUg?^&RYTx2)f3qgpe5$jg?=Mn<3l{F;<;u((IE*lYus* zm>B`dl=D5c#G@(UVnjibAFlZIKN84A==Yx(TH*--6}%*0f+*s2RN4(t(ws z(GI~~T7Y&7&=A+@ERq9hQ-Oi8B9B8s$TwI;3=uBF%8Z2eho|I5PX_y=1u5De_+!s* zfBy9IA3uBhzBLRwye9CC&uA(Z1nMzWSNn5fYQu|iBZPORA_$OYmv12JK0(+Tfdp|Z zj$K1w*#lmg8)1YU$R&w6j1~b9Sa@Il#!xi7L4aitcz|w%5Eej1a}HRWG>pU>ifNfs z${a|oFLO$#pIm$TtIwZ(^xEl%A7fq6lb7!yMSzUqWLJ=CwFm#K=>tT!TU$GDvrY3W zsTz8fq%WK$^UKgqYQMmb`$EH>49=MJ~&V>86Qba;Ly$UaWzbQw&MBh zf;=Em$Y6MgguiJ@8FBJuBgp^?6#d)pvahy4X^@2Ha`gpcpg4ji^;Aj(Vl>fqv@wvI zM{;xaJqfZMNb@|AA`S$98ZO07=>71%!H9$E9EqSsfANqVT)s@RvauCq|5m|lws3I_)E_XeKa=#vQM4URWtYv-)~@_ip(e-h z(%T9^J4f1%{pqtSH&0*uJfIS9Wv{_Z(8UP~)1)QHs6(6^#C@36T?!8$QtU@d3Ju2dmeJrmAZU*-lDtk3m zfoo79+kN@B4XQ@ST24Yso`VYN$2yy~M8qm6N*P={@bizgID(P@S}~xFeyU3>zQwPJ zS|`|lcZ^x&_col+LP=n2XEnK8&^pwDH3z9OkMri+I$&H)@bsHU7zJhyULMoXtstA) zUNU}7o)zVk(HYq^&9M$$AseP8SWa9$0D zu4XRPSt^djwzw$nfkygMZHv)JwfHg+HMj<3_*mlX!_H|(ES@UVy=(vw|C|WJ=por_ zF}3idedCa)17u4ZH0BkMVsB5Y%=-7vyN4(vi3iD`4Xfg!17ftmUo`O#eda79fJcb3 zdj~-yOGz>A$rwi;fT^GezywJh)F}yWN(grPrF43xb`~QOwA&MZvP-4u$@Ki{EL52YKh9P#e?_h!gkzc3QV&p*>9x`n4 ziTD|m;SY7+t4-Z$$%EgL4h^3~i)^xkMzw}IV_ZhC7SRH(mR>tXFHysd0Np-%_RbHV z{_~H5=nRDyRL4*AGlXS=5%Brl@Kj8_`+Qm=$)0`l=DES^Tog+^gjl4QhY&ZM&YE)X zTSr`QSF-Ja9wWEQ9GscvSI?~kg3#&WMAj6i~$M@FC3ZaKtZ<6n$TvUG~yKbNC(1&jT$ViEO8uu!%3NpWd{>7mX}O8 zMykfVxQHfxp5tv?7+@N@5{X9#HXNOq0K3#D&e1>`3q<_O z0wgwZR5S!qd1#8Hl<6uGD?I5CO%F+zBty%+`X0%|qrm2^DI;+i*G7kG8gMtINY^3L z7~BOlcu93v%5fjpMh9*ha9XL0DsxkHCYJnAXV%6SQ&R{F2Dvd)>GFr91?)ReB=WK3 zqDsZHVzdIDUViFfyfLhlIjR{?Uil8@R{bF{GteLzQ0vK~`l*aBF;x$clGdqSL4nv8G6&*h~+naaKO zDt4s|jhap?O`z;z#cZyV_MxoR&Iw9Ah+UxYKFx*`Lbah=7)3V%s*-`5_{jR1(P9iz zrR_b)%{IF{unJPLz>ms5UMPqpq9*37&>BFyriawxYhg@s zgkj;4Kx`5(2_lV!chd>&B1X?81fnsQ!119q7%@Muo8vJvFI=Hp{RGx9uxC2gQOQc> zYQj998wtlO1BZy#fI@We#y{g!Pwhpq_iU_L@^8I0Se_FdJIrHx+_vTBED%fMwjKOy zQ8rkC3$a01Cl-h`jNXcoRW@t7(A~5JSQ-WF;7_iDg~a)6EN*mb-0{d{bdf+URViwE ztA#14fr^i*0V$Ti!t_f(Pd|IH^iWtuK9_*-Z4HODn3UU_YNBVMSpvcYTJ4_L;nAfv zpgMIa@m!}GH87Yxr1emUEDE&3U#7vVGUc#TiS6jv(HdG6j>liKC5=JL93SumDfH4s zCX#Bf9S$AMR#npDm4Z*KlL6P*^!O~FxVRE*hMJ0(GFa|$GHF$Dhx113fX&y*mYz|P zKW>Gj))F$L{)QQxy`g0H=c|i-s^wY~kTqN%7|dgp-$`Lp$h+hJ_o;E7n`#C;p5)FO*e`F^aQ@V8&@lan%tdj%}~Rd?0RiM9dpO2-j>6 zi=k~u9Bo4xV$rggsF!N4yp>`vfYp@#j*cUp3+y%=N?ie6=sVqwZD_}t?mDLmmYy23 zlIpH_nd;ubQ=F3_up5YRuXrX7Z6rfx(C|luK~zx(@3Lf<12lRjI;eCmQi5-0T@*(; z^ij&2ccGTNVTH=P;f~HNoeydgvIlbF1xFE=3W_OssvzPZdZ&jl~m+Hr26G_z={*)>2 zfRt7T&vag-$VL3*7mvTf{{JuR-zb%uyOF3(aW4|J;uKMm;1*{^P15!;!?C8+b zx%d<39f-6Z4RJbNC_7Oy#}ykh$2&UybUtj5op&C<^A{@G@mt(1i&PZms}nPkg8VGc zTz@zLOP(u$q~z!*)cHU{a@Z<&>mjPA{dByM6k`e(LEDND(_lL~5OprJvQ+5vpGA{7 zF!DbAIh7=m_W5k(v*9e$pOwP+E#$&?bUf;Om~klJ9= zv13QalO(e@b>zT$JpRf;OW0j7@sj#aj>LsX%E%yjA$!TMgHj1Wv`#Rs2fV_mQ>gzc zPMssAQzwkk^^yCeUE|Ry)PE(9&cK>Sr!!CM$vSc8i0fa+opW4t=XBC(J+84YN0?(@ z&bhKXTIVzT5F>+WuAB}#odf2KAEy9U`*CGztR@Mf7v~6Ri*(rO90+GzI3ir*M&S$z`6BJw_bJn~~01SeZ%tUme7vvb}1(hRGYcNVX?%VI4 ze)Efe{BI@k3@?bUJ*=Ija3=>lrSrCGaCYAAl#^1curhJ#|_-1J7DG8y|Yd^%euLe9f)Ps~he^xfVxt-4$ zf3>s4b(=!9Ej8CxyZkBFV25+zynzL_-=L=_qJ36YU3}>-16{iU`Y|MG+ zi8;wTxMp^O62UFi(Q&zQ2;D4uXR|?Fq{G=uT4A+5$2Nj#qaBNGe2lNaq5HW?h1O@H?4PxP!IN zF9n4A4Z@CS!z+?R`UIj^dILl{;4&U|9y-1yGIMg}=ZF&PokkPs7-U6c#+9htLz75; z((r|ZZ_S-ozGRps_r1;oP@6IZAbM7=Bc-N?bqrw4qICcQ9WVxflFRcQ>)wGGC`gA< z=ETkeklpidf5XB9G8K0I$S@xAkpu6Aj|25xiscoc70YE>?3@7KG)A0xXgplVQUrYU zrNwn9Yvsa2`-uHsfJrMOm&48p^G1N#d2TpjQ<09ZE{gmxpbW2FGg5^e%@5lg~ROO$??y1?Ba`{%%P;US+d0u;J}Wj!IyyAS%zKi+Rl6j!<5T7$0ydf(nU^~c zHl3KD0_xJw>`maqAT_+fh&S%huU0sq=@4!@lfqg$LF6``1?dOTnhW28OGt+!j# zWfdWfjZFHT6Cv8~gDzy_J^CPmNTX;qeeEbKrH4zyYFVjKwRvg2ukX#})jKi{cuu5w z?}{Uh@dGMXQ;M!e+C~s4hN}8Grv?XSB8bRc*pZq|yHbScq66Nbvx^7ykz`a^K z`{z{TK!~^@H)ltLmPS!#3>-0oS?I0qbdWa{yp@BP-e(oH??LkCl^`u}@M z1tKKwIT*AM(`V}fV^T@V<+XDHg_3;zUrz7nK(T2^orv0xW-t)aVM#=T9}!mTj&mSt zM=q|dhlPJtFOoqO3I)rY0*KtFZ>wri`ESy-RLj~$?N|<1W=PKgPRj^Q6az9WfOC!5 z1!ql=!2##k#*WO8o&%g#5L$5C29D$kj~S$5o^ow&$Ew#dReBC^NG^5)0#utwxmj>S z%K2D-6$MK!>X_-(_tf9RuOEkuomS63!3v?1EBN`EXpgO3G`|Qx*Xn8HEu#~12U1}1 zXj!roE|VowhXz|TK8EV4UP22%Qu+|-uAKbPo_j?jfzl8S(;x6=Ac@E=fU#hfJT!K? z5RR{;XE35;oO6h79U)ir8;;H>O@o2@Q!OKeJ=dTsyxd)LI%O8LI)4$D1)5?KYxgU@(5xfy;3q% zv^KIOth|sb@r;9doQKF}h@oz+)a~MYpRvqBHU)QL^~6oobT;lEwhT4cjfb&>J3dvUx1o|XQ zjYdkSx*Cbg2zGcpYz7r0>^4epQA>RQ;lYSaR1z$gJ{KdIswGQ=ohW5NHfI6nF}-Og zRl*W|aXi#`SECvU#Lt3eQxE}Iz~jDS#zSqyxxaZlH)ipq+I~Zz5gvCJGajlUj{MEz znQp@VP`6*=`ntA^+T*q3^dyp^q{L}zgzx7nvPAP4mnx%OxPF)d>Ec4q@{&X34a@P} z0?CAt>W_k_>kKh$-{q`d9}jL9KKcez(Lo8JjkI!)eslPG8$p4^6KBZsfe0(6k$I+z z>PC#5&C>-#nTjI~)&E6Al~iLS4nJ3#$fts*s}>oE?{dcQ{>kyrv?h=+Yt)DY zxn18WvsN%NP?!hB5P>I zo;7eV3){g`J?nynZZ+)DfTLySP`m>JXc)4`=AppXzECGHO=N6Ar4*81ltMB;uV>Ckd{>DQk(=l_-Q&*8l9@ntYK2+ zFzQT>-p!&~Qk!GfWU4n4q*iaTguKfUwpmE4Rp&SynCeauHILwm*{jqt-3b?Zmcj6d zl4+h<`eRyRDJ}rNt3bB;vxWr~74;{Fzh(*64++ffnGOw{9`yrr^a`k_w^{|kcmStX z=gBl14j7_wIDo%-R*%+jpiqdhz%EC*`V<15n^})uPa4!Re{&Q=r|lu3k^AmO(iYrdZ(m!}+=l2M)oTi}ZnZ8RY;)>QxKL zFH=9I3?NoAv0{TIiSuLvEcD?xq8jO<9 zDsNDgQH2Xt_lf7HIxKL0C%zc3Av5t{W|z` zZGcw#s+lbblU=+bD(-guhW@|Kq^tv}HDkQ%vofk+z+!DQFgoFt7yvY*>JD<}4pK%& zmBhO|tH`%^boLc)J77@>y6mr`%vAeG4dTq(CWru7oZOm|D>cfDk$wuC3FInQVU!#8 zm$g+?4xiMdvjL2Go;e!YQa9;@3;5h=$@sM6ahC^tR^h`3DFo59N=o`z5#7Ja$Y`wY0<=%QJi9K$=u= z2Pb1>l%>ah*1h$ibXQ;*J#L8ym*GFnLJu0@LOp6jp1+WHJq$Zc0~ zQ@Am)z+<7Eh}z9t21>=qyFhD1hif3#0BuYjA(|GFxK{jIDcf>QNqzlNRL)SDXqFL> zm9GySo)egJG#%@7@g=mUvH{jI9#Wf!eh?ywI>KV;?K2dN^YlbyyZv3q5M#gdLk9+DQBvL95-Hfk!>-mnlm_ zgtms2a5vjWPd3hGe z-NR1GMvVu3C6W68rfv^buX&h;FDN9bPTr*Fp>Og-mGV=cG5T=abe^4QeN&>k08(%R0qn z2(znlESaYVme#?9qdS3l{E=?QydJ4J)HOqxT~%X6NWHzZ4klt75oh&zA=FosI7cP! zM-Zw62YD&OrPPXfAPF}QdJj)~BXbo4IkvV5oTikpp$V|1wRzy+mYAyq*wt+;1NXv? z+w6p9bd4_0HIQUU;4J0J8%riL{cMGY%94plNjq&phu=BuIAs^2)Dg1 zQh-pOFH*oCca)`oS_z5YlEj>hc`U!}$`}C<&l@8|ke(k4AZbWs=wk)Qb?GL_~d06@ae+Hk!ZQZ#J_iFS?S9kX$hQ)#8w4{)hiwi;j5z=n_NSC5G; zlWe7{4}7V3wi;iwxrUFKB~GQx(k~{8hgn3aY);g01ZRc%NSp~iDG zh{Pm$y$RzyySzNM3fv7e+*wKD*C$urvz(lv8|z%pCRi^b9F}4h{C4wRMz?u^v=h^? zj&?V`%YiHua9Vg{Z99VJx^@EZyM!l3VI91iX;K1Df?Fhf*v9Zjj&t9xylaRrirvrH z=$-KB*s6y`X;OzcqAP!b1MtG6Up9Q0lyUX)2pYjFrY>O~)WMSl@ipo#Wq=HqW)LOLjUzv+VTYdXWG$nRv&A zaoIz5a^<5Me7ja@zGX|)UzFF4s$5N1*caeYOa2bhJX-1gn>E{R|6C!p;07di;syb$XP|9M6el=nhCjy1PjKCLVXe; zSorNPp1k+z(>K0`i2CdbsTBFX0p+F(u8EMS>W^Z#G9cx8X|q@WvNpwnUf)?9X#V3+CTDZGXqkLxEuI=AuHpL=w>KDb9W;{ zqmC6_Jl3(>xTSbL-Qj7PDgu$)U^Q$oz5~@$H5n?kFUL}bRK!Web3$#rj{sGQdJ+{D z-y_hptBvj?RVw$jW6MO$RIX`WXUl!?-^E_D$aN z&c+oJ9Rn@XDMVEBT8AK_3&%N#J&x9l=qP1b-iijga&0`L6D^lJNfYhJf%rqUKvsS7 zQlU-H%nI!uFG3CBNo5w9GTh&V_PbszTX@>G6r0eYeymGO2~K?F`m&`Mo55)geUDQ% z07u`=7Esd@-;Cs9ZyzZzaW=a|OJdP&-}J2h-o9qQc9;PpC~*^1J9j|soLoiE=}$av zxTYmUNQRz!F;GLt+=W$z0PwR{;zlFGqS4^IO`le~T7Ubg3Mnw*F+Y~qGeG8)$0v)Q z>45mJt~-iPp0Us>aMq@eWlwL*q9S-zJ5T_ss*M1uu>lcM@yd-%pjuICC2N#{(F$~6td96~HbZ>@5JHXkSKCDZUWs_x{5I(^A z*~zhHt&hLOYX)SE1qkpw3%y~ptwyVXVD5frWV8aDdg&uOJURZYW~Dz<0j-|{tXAg- zT+ZzDIbfJXw7f>?8b70=o6FgkKC104b=ov`7w=6z1VEL*SOM>E#O4#1kCUoZSk%5CY_yJV}mSkyK*Ok>!#cR$*8?VHUQ{OwSil3W- z(a+`F%M7;70Gm2Mj%`Mhl8i%aT~*4lQ|x;i&~1$_b^p8noX%gSCGeP_TOnFknKI#r zqYndM(5AOliJArr+%@*H@z2FiN&c9FQ`JLB?ByqCA`s7OUx>fsSgpd`Ai)NNAeH>6X1Z)5&iJTRd5UNTAJi(Kz$C81H&(C$4j6m* zJj0RkQN8jI(G0#z1AC-?Hz;~H$!Vi%$$k8_7y*50eel9@T}2BgT;dZBU(H}VSc5Ui z>##GDkL$k{cwQ{cBxKHmLs>KM4%fhgxD}7Xw<1mTFG=XY_@4z{*C1F3zza!zc$=+7 z2}z_I#sPh7B>+gIvjBi|TQk_U7o{U&gIIPQ+7aD~_n)!RIB~8BUMuu zWFZPDF^ZMKt8ytZK!!+nTl$vfZ0iO#;`YgL-%3$1-w;~k(l^JpCfREfTY+wS}@wA~z2Yk+)r3S@)a2_B2N z0neS7#5D%HgG)RftxHt3RMTULK(yB!O`GBxFC$U$A6jOqT&nNIkVptx?4O(GOeLCU z4x>%c%xxi29K#M_;a~Y7e<*Hkg}LpCIioU2-jsPUm;6RIvu`i6U~3h{6hkq#i4bON zIYO2QVWZl<h@HFtsrk^B0( za1VYhZ>ev(lgSrTz74CDh!OC~<~4O+TQTaJ(qWNI#uS@O4)oK{NB`mb|L^zz>+`hM zB-Y4%aW)fE#91_aatSDz-%2n1mc+1ecFOte;K)d__dU==MFGWBWHHrgmvREc#S$sc z`CSlAS!@yKP)9gW&QnbsoLa=M#G|>4vs4ZgZN7ZFz3(mpee(O`$6x&b9>;_k-R~fB zbBCfPjh{uU)E|Yq#Tlxg=?TdNo9T-~Vhr?uS_EB^8B4g$U5Z%a=ZaZA72KZO2wXu^ z5C_DZ*r*&5_eO~=87%m<%p+r0gJrveR&2bLAVwxY(1}`Ln)D78eO>8EcO{O7xdz&D zT>CGGvQZ&N>gux6?hVZ&NE#k`s-mb9AVo{zWpGV@v&wWA_du-V=8`rQ` z(}cpgAfli!n^O~FYAIoZ_LYmRLrc7HAExy{&rC=faBu!#gb0YLcr{x0UVD z%V48$*22uRZfj_b0o1WR7ur?jT=dUR0Y`hswldVds>+N0nOow_4>ZP{%Q_e0 zpb7Bv(Ra4W43dt_dtuxaRc0zPt~=c0@u%~FJV)^vu*W7tSX&cr5_RH7@Z#JR$h4)! z;H{|h!9GV#8nDO4V|`89s7=$sgHq>%ea=!fpf_eDx)UYwAZ7$Vq8r$?A(j^5;i>aM zKNvU_B`~cOJ3zO%KLU=Q3E6)wA(W>DGp8`>=WVL<;XY?+8@Me}jacJnN6h~$xVwhX zirYM3buQfJs(AynS;7c3e(r-ViTFfpOT;7KIj@4=oTnJBy;pkiw|3@jQ8hJUt zj^CD48wWb4h5H$#0}r{t94k52?g6iauOUY%8LJLZ>N$>gt^~Th#-0?&=d>r4wA#$n zBDP5Z4tuUeyvCGN#A7N?O-Vr9r!mJC_jsTTK^vAU-PaavWKtw1MZ)Lt!%yJ!mRiKw zOP8N~^1+igejtJS-~8Ry9&!lc{%lLbv`EY#PIC8$9SSH^mdsTtJ#1^gbiMw$$17#a z&~*J5ymkCZBwUai2KFda6X`||w{dxi(N;sE&f|?j$cds*GSCVLrrY*)9C8I5%tAc=>e}gx!k_;aGI2-W zE2+4E4(f73t&B}L6yA(f1+)pIFlwpaR)AFf`#J=<1|XKJ-F+FQ#PqfiTjQL!*ebLt z;^k6AfTlT|eVu?@0SzfT_Vt{;c>U?4UjiT`+$&KoBvZn5*i5Mlh_&op%1{*>ehVV9 zud|SAu;2}ys2x|?;;Dmuz5W8Hudv^;W2hy}DpNzE=T3k53L zr!W5M+2s$P{puE~)}-WAR}P~Y7g?d@cck4V;`4AO;8Ux!&=<%e`5=iT_mn=sG$`r@Go)23CRG+={5wZio$a(2MW{hoA+kzBwHbPx; z)&Zuyw-#`h-+TM;Xbz(k0fKa|b?=3cl_N{rOhBkfu6<#PHEJ3YYz1L@dYbWr|2W0@ z$9eP>C*Apdo%3Ar;9L8Y^VjEud}}`wbm$RLimaVarKmDzNz7KP+@G|M9L_45Ztp=~ z{))4Jt7y7H;ti{`ZdRzJdA>rWueq-Spex9V{~@e2wUKxg9oKD~rf_`(8Q=O&Ub9o` z8v&jA?)y3dx&qzSC3*B`cdo+l$VvcQdL+b4`K^c92uBDJw=!NdK%S4N7;*MZ43PYx zH3GDCAQ(?y#SzL+KuQC6kO4Nm1|91NiDj2Ygy(6808euc`^dnog15bMboTNGXSY9m z^8RmWapt|BHb>biN>EejI1!psYOFvx@v^UTpex=QO*&|$!V#Z;{|}(y0A#0&AW;+W z%8!x;X`h)nr+qyFw+7yU!TX#4{!e3ko&3@=zV@BDGkRYqReSibYys@n1Y*UcFz8RU zliq83Z&h?7Jd?q<^j)Kx(cgy@_4%%kM>B8gB1*P?(YK}*miLQzJYS#jXehO>)2Uth zy#DEsKI+@$$moLfvC5s%XU;}o4Mhh}rH{mE|2p(BJVqa>5XgEz&NzEn`{>zguRvj; z>H6dfe!eDbvyn(WvrVK3KbHn{Ve|dC3dXqx9!2UB+~*ML6v9Rtt@-88Ffs7ik4~>) zME0csB+{b;#CKo_5O7P6VFDDRhA8!h8dxl(9i;dd6e`3ZfnT14SOP(OquLZT&CrUj zY)Y6AzMgI)hhC?Yz#a>o(w~2l=pPIy0mC5{lDkrVOe^c=v@sE zDSevf@lLdWiGBfZyiGBoy|_mo6&>T*WD~+e5nA0~)FF^th5&{R0Own$U=BtEija~S zn1|G-keh=5ie_KftYu)V?p6kl-=E3_`uza}eWG3G-`!9XE4OLFcQd5m?CO*taCVfKo3uzETGV% z#zGeD(B<8WLZWh8JiHoJ8D~GIFm|xQ49mj$Fu)D-F!0}Lhd~!b>lpFE`Myqi+9qan ze4_&ti8h(Dx4xsJXCJ&)CM#hB{rH%41TXq&URm?*C`n%U3JgcItBr@*!Pkk;c~F}K zN{;kh`2eTbPp;gMC)*{o)^m$bHbMpqrfszu8J)LXb!G2s0;of-orLW1SFZ>|b3)c& z#ZpM4m!HQ2>Kpb5=GBl4iu-Vtz!dwcKglPljoe)v{?#VGkLCXz}VI!g_N zM|Anmm?&mC?m3TX|LF0<-#)u@vnp$&P?YrsjjKm-S?~k@Q5eeQJ!7HW(uvP`EV75J z$~`$huu@Fz5K;WK)VW3QWC=&m*~sJcc!~B)r$6UGp?EcvJ&89Yc5GiwafYud0ZdBRVLC^uTW$E+m(hRPt_yt^D>9h0_9v;rX ztWtn?Iut+mRA)d<H7kFJ;HG>P-fkj*A>u8m)+B(e;_vJD=m5=v z1TJ~9uFlo=sadLup?I?y7iCZfNe8$v*!lLmr{DY{XoK9mrKB0qCnU-}pvpMtPwGhN z0EalWuOc@YaWrE?WFY>E79}}TX=nF@Bvow}w>Bf8TcY!&^GM*vj9gqdzKy>WnA=$h ziU~gg2bG?ygv*LFI(|C9B`1$DZAaJXeVq)lV}OBXuon{tD$WE?J%-2jRD5XXNRdzkK=&I0h}Qu@A+uA3MV&92N!(T#aLwt0&^LIEbzEV>Cn{`j87_-Qw@&Z1R8ti`noj&1_f z2olS);O;s&rJAM+pMH1@U#dXFS(DQbf0Kbm@1X&T&9vLP_zWQ`-K#)EcOC)}skL}{ za2DrVj#KtVFs0eS`RI*|(<1aK|B;}On^~By*8)r`HIB^A!o24&cSbN@Kh7|RS`V(* z4*m~lJLL2>)PR#Jj}x=AIMJE)hC|&QKz(|QsaZB{QLJZ_AStSj;tbT@Tfb*+!$o8D!#c&PvaL0QkO#W(`*jfiBeu(?80UTpT z4zSLFyEF!uD7TQ6S0;lf_VKj}*ebT1U7f>+bm+*4fm!Oyj`N~GCJ6iJTD@!aT28ah zp@SF41uz0P)RVfo?)d4}; zmRW&HEX(9_=yrtZ*3r|euRQzl{j+<=e8vc=Nh}9Q+n~!vQxqB`DW<9?N^&_sqMo=U z`z(x(wl)V_!m1;x_%jIu;YVF~<1Lu@OXN%pVhruFrV7;q zg>qO2OB67v{yuYWfA#meo1{#vOf+G>&-{`Z=B?eWOX$}1 za59i5d~}%LBDI*2&0QmGftxNVO4kU|PQ4c8*(<>P(&H~)J~@W(csGNSZ}pams7Xzq z;`Q_ueV=~cCqMHH<1AsO=P)MI2McT{V}d#vPTFeKYGAC-5P)~ zh|=lR6|X9;w7n{#rgt8QQoHpKLFt5Yt?v`1qK3x+%B@jVB1-dY07=rr0{Pw6Mx>i#i!^K{FaDpxmY-UUW^nx`_XM2qe7zr4w=7s&jnT*tp#feh%X|IHN<47^$1e;$KHGnZ(ngs|wQ)Z*9d9SO=5T5>r%Bt5sEpvhN>>&!k!w?giKd4 zCJ8F-iB5x_1L|97eE;C-XGpaETymO^Zl7HF@a(mRR`N8u#$?y5ENK^pMmn)@T{L9E zO0BH}q36JQZx-uwp)G=r1W6pzUk!l{K5xD2SGT$;v)49-d0N0#Y%{Fn< z6+#3%AV#cF;dh7(;S%}cDcVUqQZir(tR=z9Na#xD1=u$w4@Ji;@GInI=xa+u1yfLx z{G9HjECYcVBVFz?8@e7NlJc-m>O4k_W3>+ovhAabu(;llS1nPd_dw70W|NEsUY4SmKYsB-O88V{Y*Y z$~f@)_g`oKQEeL_&JI8<7jC-tAHR!8=u(*wUXON_3xMYHDcGc~ytm&y)nH2Ng661K zxuE${nHpY?_xx^vFL<8eW2glD?E3*&=WH7p6L|FlF6oWcbLg*|JgAQ42I6N=tL`2V zm?IFtCQ1`+-w`PNlZlbpym+aM6t5&T(DoexO$g0rzO}=1-VxaG;#kJ0j=-fdO}rjs z-w{CT`-opb#Cb+X| z{8R{Ax`863lAL`q6k{Jr$<08yB;n23^_M5bkASI-ZR0b^?mu{v21 z2iJ6J8W|7GQn^0cBMQ1UZv5c%<|pB8e1S!vNFa+P9_(#o1RqEWU0g?I9AsW_{5)M` z_0V16YaS!sRWwCNVC*M^xJQ@qXd3cTIc(dZ(4o~sw)eirWmG}Py{DniLZzuv;fMmR zx~9bO84iPra?!ShgVNFCukSs3?_oqS1qSETQqHQeT=5ISCsHN6Nd^I8fuPr zH|X!k(nN6atzvg;#A7s?$dqPzLzc?hDKap6`sGY*hfHXk%RSs+O3T;|@Q6lh+irTqcpY5Q6 zc3$A{g|IuM270Vi0`ClrYP#H=?EsU3k7PC)4qpheha6r)>VPQJeTQ*vb z0uN5#ZSe-O#T#&#yR(RR>i%gKTfTjdH z2@P!rr-!>}_K%+3|0u{fVeAdtc#1;?1Z|$Dj=3Zx)YKdp?jpge6X}t${*$u@zdO74 z8FqJJyGNi;+g2S31U#aCn12GhDSHuyqORu9a2FT0o?QFElY3FJ+6;a`;j%5I za1}A}bexTgg-;l$mN_ik#emaZSKnY(E)XN;EdqdQ($!5=^ROdlaB72E&08cK)We() z?&5&1q?*~FK~1Z%xCbI<7p-^BxVX)`2Y9AL&s#_4g!HmdoIXa37dlgiMNO zj1b?g%29FEUyO9*Bm5C+qMbOySmLorInJb9;-GLJ;3lzm0gHzPY*|AQ9(ExOWU3O+ z&vpyVzc`E4Y)}G7O$lC@)7)5=7v--2WT=?RBp**3^GDwqnp;1>^a@)=&jtuv~Ao&1CTUQmw=ZYax z;B@0Aarp7&?4Q;>kyUNiTugc`DoYLt_mMEG*&vBwQHw;BV~su}T^5jZ)g!yiFL6w` zj|4aK24xO^tQZyrVEQq?75=~QQeB0~d6P>#NsG;CALBNp>DpbUL|LV2Uu@@*A@vp zy?^cLhwphOGT_~uM>|gRldfu?i59g$w~_7vXRtd4R(o;=U~l=D(H#VyT&c&GjzY>e zj^qQiwCP-EsO0N(31&catvFXaz;pZP>F4MP#80e?!QjP9XH#HNG+JP!l<9hTR}6eO zrCc~NFh=IE{ivu7fXP{Pn=(S&ARAF0eFHZKlYyezjOW}E1yY5Nrtr%IAoNQOA&Mc& z=;-Tkl6Zhld*>z+(n>F)g<*{2;wyoU)|2Y17K4=FnIY!{@fcn!!8?t1=?p1G^0~}P zxX! zFP?YV?UAu7B4+71LcD;ZJKE-R`bcuap%*|A-pw3I_KK2tDy$YTue-qF(!Uwm@wwYZ`5F_2EJ}%%}O+Sn7>gPG{wUeWnBHIlI%{ftP;Sv z;xR1k5NZLn0vY*>X+T7;%uq}qbtTfO0jRT&?E@&K4M8oWHvEl{0ESLxhelm#g`Y2Tw~|{(vzkj^=g?3J^+;>Z!%OPnVj!-E3LS>4F)3tj0# zGhr`>CQ10pT_ydUxr;ssCyK|=Zcp>N0h-AFVDz&*s01<7X7rK)&%mf6I95Ccc4rtq z*sFRnTy0S>Ncz#k2kVl;zs6NX$%1EajIKCfJO+2ymdi$BwQhK1GwLe;wE*$fvJ8Z| zCml4N_tbW<6Cpl>hJixFR(iIJdyCXT0oQf7tugD;Sj+G1Pr+>(CkS!KQ{xveq?25#%DMx+-|H3+^iU%HbMh#Fg8aNG5 zp>3f4$5_#PucN1>Qwh=D$?-M42>i==#8E(6PJ}j5EvK9~nQ$vok^nPD#B;i1z*KVb zuL9Mt1R};@w1HIgn;5JKq9Pgz5w#o7<&F{EQH#()WPXN%X%J1U`UF!EjD(3wjYo0E zn6OUjS07^PS)YGGsF**BYR%LLAMX+q>;kK5k+O0USLXt^Q8IW2cMR(-Niv`XH9TAE zXarK3FV+WB9E}8qu_uq)j=|WR*?T9)zt_#QA3y%$^4Xp1l-GIIk2XF)CH4Axu4KFu z-7MqHgSLxM-)#E$(9yZPcc}&^&iNt!^gMTl#q1VGix*+3`xrZl!_#_Vfuw0@)yQP6 zUDUind|UjkR7g2fyod~%O&qoRC&%B`s*2NWA*WCsHK$s@*41Fv=f_#%MQqYGUipq3 zS6i_iq!kJS`2r2yBhn~c#57p@vYQ}3EImoM$BR}0qHz}xkqlliVp=(Hyscshct=*O ztkKMPsaH5oyhu_>bq&^|o2kp+MD@mtR>zsI-}g+>4HCdjk3+1c90 z99fK5X7R{l$scr=(j6lFC6cbkX~%k0^%94Bm(k!H7u=vHuU$WqaS2%fZrgMMI_q>$ zVD%RgtGeP6>*4JxJnM0oz@uX0T<;2azBbk=Ws0K02;?Fg;c2xz1ya?}$+rzmz|a@t z2=59og;pn5@uyKJey`Lz9be-J1$9e z>B;TIj>ho5b&Y{PeRIu~Yu>*RMYL=##|xTZdb_ z`c5@g52P~Yk}9YCpLk~SYc=SF`L_;_u+qeLT`95yPUcdj)am%Sqhvx;CvjXI{r@ed>`N|dCE%w=vr{+389Wqzy}SOMxNK4U3eJE{@S6BYi1 zpaQ0#O}(>b0!w-RfWx)tc*4@3<)}uQ!Xy!}$nSx#td>3jrS|ziXK1AdT{nKn7G-cb z&eeV@?IU{kz36xBcmEK}fIoe9<>u*&pZ_^j61L+&*$+3nRRsYrM`O1DOnfmpNRGcq zbUlI+Rrjh-4>&D*Pi+pk3tfeG{@q{xt(ig?2il;z6=8cv`ugNamhB)(6m+Tk(&({3 z2Te6P2ON8yLm|y{*&rK=M$#z#u9#?zWf(Pp+diP zcqI*65#7J3+Y7G+U}NngxL`n}W<`Mtz&!I1fKgFPYuP+7S+Wmz6TE|YKu|1)UB*ab zTPrL*hsslO0Ts0mhf3$jg}#+z!CT)tc!5L*}wOwfW*%e(L|CYmq+E7qb@k*!7yDM@JM-KdqN16kz?s^+S9 zFK7nl_^W79VLrHD^Yp-qEaSHZ@jpCzPtfB2<~9cG8ok}3RR$x^2cO(jH~t>o z5E}cS4|v`c-*-W@7YvP$AK|A%n6O zqB_122e^QUE86Hccil5U(0qaPc{F|e^1$AF z3Vflyg?exyU*K`xgb=iXIt-e=eSEJ|utc*rAcPsd{m^Z&<#sO!n*Y3U6$nOr2`%C z%wIltrc+TYa3hkz_~|+;p>UTgk)J1*4n(Z3X8L9%L7)BV*4d}n(R@Sl z`Y$u#i$V?fv9on8Vc?hZSNAl_wzc#QZ16eI(aik)I8^4}&x!*4D2BLF*}d|uO>rRf zRzw!tEpVwSE2dN3e)ahC};6w@QK&LY^q6?faRcO)lt%cTAFuY)Tpc9%I z8WfDN)J+LkoON2&628AESF(<}an*m_bd?L2vK{DPW=3!zmx$Su&SRYBHbywDc8PEx z(fDaS6l}Q&Z`Cz(0I6@)c&oT1(1oL!3a3sJ&jmCy9Md&;-28T+^O%_^&~YemN|GWZ zCj9H0Ds}jJZ9NbS-KpX&Tj7=^iDp-)FcXT|o3g^3(5CYNB>uZPr=l-X`J;B6Mb*N= z-2u?VC?O|M9c8@AEL}$(5h@mRVGA~QTK>L2K6<{~^)&UHnP^0Q+a8j7Hp**qv|8Jl1zP52oQRWo8HI1=Nx zlr3x6;V**kfxpPT1B(VDn6Z&=F7@#YiMm-wG#4SE=_f;?u|=XHVvEYTgRPcSr!ALd zW-KFQSfaz5iwM!z*fy#<(V9$L;(nEPxfvU{`dnxtVE)d=YV^&mL_U9C;=C^JfGSe@P+ zqty2H3HK#%rrH9-1u_cl>JJyvG6j$@v9tX^M>iKSp|?Ftgl0GGQSoG|($0mctI5ps z)d|i;Oz2L^m{1HTn&Xf1#xtFm$kAEOMNH7His|X^(S`9+d^lf`)aU#%h}E;&#TU=G zsAP4ra}k&HB+WO__&Y{z8@Nh81}iXO`Qn~ucvQF?@f_L!u>9>K*z#xhzK9PP-SViw z__?~EvOi+EafA5m4QlTQVTJL4G&H9f~Rm!RA|dh+AT z6VgNlA3^Z*43w^7H#eA9Uzebu!Bbx;v32X2M?~eBgs<@iNy6!6kRF;L_-3W4>7;RI9j`v6QKigvDHCj$jUH#J$Dk z0JO4x!9v>`qqLI_hOS=h)r!m5Oo36Ttk zI-66LOK@Z+spXx{mZYE(`>a#8BV{mD)EuB(0&~m6J<-PWa-TLcP`ompp-?GvZgL5V zG~>(Z@W{o!6e&RANB%lt9Z3vS*AfFMwm&?^Rvc3~;H=~lqV&#D2l1q@ZGylnFKPr&YlQu5-GM{%< zH%@>_N`<46OORs6tgEF~TPGwnpvs11r>n5>yMl&Ol0!7NI8kaU;OsgIxo8S~>Ez0d z#8gdAA%N4H!wg-7wZ4P&h~%T(4cPUxOAt|FAy3+b%F`7sVep;PBuO?^B(IsQy~ z3}r$7PxN;%iB!@cXn#Osq}G9@bp;&|kHq$&$8dRdwfLJ10yWRDi`;n?)KXjBWk+oXXt2DRZABH}STiVw7;ND7;bns4`aC44uEc z09JAL(KdrIw)ao2zOGz^<{do;WpJab-zal~3wab~)(*Y^*ey#2Ov|D(nHK;I#jZ&+ z-LN73*$sVL_dC*;Ivp&a8=N&Lcj5DX6$pmTH5oV42j`90^9vsa5*p!CrE27jIyD+d zeir(w3F--->(&gHx>bicFF;--KH7AVK{c%#s2Kr^U@1)%s+769r@=BGR?r5`Hbq<#ya}--czIX${M?|sPFjWtph3N9iX>vs3?V%jRo=?A zu#A`vw@!gxjK&vI$fl8rU0EjZP14BVld6!*$XEV1P$Bam?S-hmpp!e>WdhuRQZ8xj zjF(E8cWN($cUdnltjmYuz|X#7E{X39nu?isYcB*^&P{!*4hVb=X|g=I`Xe@{Mh3TZ zO`kJtWgvv0V^GSboQ#|K8lJPg5bkB00uG%@W#o^6Q7I|>jG0D-7jG|wdC9JH7U?8C z0?6PGqw_?MbPWqPu^s4~>c#ZP$hIsj*J+FkfCt6reto7fjf+mOUH~xj>v=b zqUaeVt7$Oef`r}ywTGS+KptE#2O&a;O%<)*5#s;u@}QDi6!sf za`qjX4>}gi*i1<3Ii*Ze+76wTy%@ytQX2;yW0F8x{pgrG?M$9y%9v?8bf)%Vm}RIN zfi?K`8sxbo0?^4>HA6s21`uOWe|XGPT6i)im=@=3?H;oh@7ZhzMH)Dl5 zN`@?8Qfq{P^%=@t>S6l)XTbFI{>y>qhPH=Wl7I}@I;gzv5m2cCI^^hYhf*>T2&WCp zF|gSL@t4l!%SMWcB*g4o`+5{krV=^&R1+CnZgm1EP0by061W2jtN&%OJ<{{A%vXlE zh89N1RDZaoE{6aDAT=z9ym8wBgbC*8+uEF|5U$h`e%g>MX5HpUhT%VTd-I@h$+%U7 zOt&5K!fg+s90RD{wMO42N#(u~<5{ueW-WYGKfT3q>7%@sufbr(=miY`GkJvpYY**q!T zSA0=<(cmJK^jJVfgTOiB-jm9J09HWAp#+tIdzd7SXd1mAAh|CsWTLWg_AfcFfP_k4 z5(! zIoor{(cvCtWH{iu5=VyfHDZ20q8TTtTw4hop-tc)k23;osMhrgZO?4uwnOC~EXjH6 zcV`bY_%#<|yj_~lWcP^ZR@O$1vIZ)kp*k1H{g)Db?%XV=0e%yr8%Gp%IZa)7EN7g; z#NeR9I;|>aGVG8;%)O@)UETqtuD1g+B_6P>1>|8upb%9#=af5u(8fQTVR1Y|&Fut& zpwlY|1S-j_iier3Lk=PL@W}i&R)xTjKv_%4G3xQa)cBf_vThtG(RkK!UXoP-7L~Q0 zrtPsc=C(TZFC&n5is~zcr{V(wrw%d{0!1Y5s|O@h&_cy7OUq@%Hu>mCVhy=LQU*J^ zP|&KNsjvWr8kqygy|>zRJ^1hb^9%pyKg7o|T}%qJzf#lqujO4@|C_0zLrx+0AlQ}Z zsl&FR`yt^KHcGy9n;QZm!bU?!l#Y!5S@5;0nNv}R98K=wlZ_l5dEYT)Z+z7vOWHc3 z;^!G`t*Yj{)FG#oD>i&{*}IcHkQDT7E*SJ7p7Yxj!cnq)2Y=Hf>f^NX45q$qEqKz@ z?o{MDwzUTKS|*wRGTX{wW%M`Shl5d90I5TZQ2DZ$)!4mrO(fwO^TWc`eAu(<>q7}4{?irPEC7;p{!#y$^~rk4!K z6q3~9r2{-t2+pExb^E1GTEQ{^1j;gUs{vabW{iGiD1erLC zH^tb)S(FNb`mOe4jiVTaY=$(hnlb=7im){;X+$w$%MF^Y;F+nPYTteV#Z%WJBwk_E z&++ETKOZ}}Rhqe|$-cP>?*qRU!GYlsJ1(=(MUY*$aK+1r24l#s_ zX29tbcff){;sKUEVM?cH!og0_Lk=Bf9J^abr@wjk>@uJrb0gh}^#qET58QJ-H;B>< znn17_^pI0V@Suxfct5U9VprVu(bF$pM-L)95izX{vTSYkux?NO*#kh<@q#~tKd2Th zs0qt~;4hMu7`|-J-bnL+3$Sn?@yf?str`HeJQMiWSSD=0uO`-+W&()zjAKO$fN&7e z0mZJ~2(GcSv*2_OO^`A3{U_*X(>P?bfDS40*M5pYW(XR$NJU(uS9G@ol@DF;7JRlo zDB3xW8_j}(`hlUn9YHh}KM;o?4w$(TtseqMPY+FKLxZQIMsUWCKZYo!y9!bzxQ-iZ z_%Cpp^kv*JLrM*J_O^5!II(uL`TT4J3_O$Y_BKk|K^uF1ZwtI#rn;f&QGh$tQKJr^ z8%K}7_(1NCKlt70m!AR(+QI{x$T=M}zAs7_s0r`Xyi9S_G8!_r4)t`<+&bDld*@%y zeh&KRk~^bG;|zr-(x!AdHOe|40HZmm7>TksDx)B;=p0W6#hn+9PTxI#^7iitq6DD>4|m6*@xG0fPpuHsxSoqWx;Y zfF@y$gGhO$OK~z16mv3kQlJ21R7MbV{H_SP&dT&b zEy=nG`y8T)uqQ;GBg%*<>^hv%M>Hev-X5ybRDU5bc`jvR?z~FkUJ~UQ2gO}4clB|s zmUv`w)$<568i+iw6>rt!lZ?znp3@o~bMc2q(o8az`lW735KHORk+iatDx;#s}kGRW=UOvi;!QJq-!jn&RZEy@3G7!JKtkoS4p%i?#%;Oi->juin{p&MNB?9Ab5m3GkFQ#{q--|6aY{I1|Z`X56AUHV{r zXta8UvAypVm>O99|2^Sht<=GHtSbc)ji|hN5a_l~7{8yA4V{(LTf2I$B6=T@efar# z_=@5#GKHpzk78&`Q3yNHWixu*HGasGJ6$w4aqJQk-Y=C)MB2V>aAFds1IGu#%$7j% z!g={(_!Ay-kaqH-RxeFCn7@wHxn&y+Ca9FBNu!u{AgIVfBvHu?Nwi;SRJbT%5-viQ zs`l)3F}XpBq*(~+Nt*Cd0Y$MR359s~K%q6DI0Lb~1J`&E`Jr zy~eo@QI0^>RgN|H5fN$b1MfAj9Dzs=z2>#Bgd;9<=|mx0$6o~tE+-nEWIJl^BcY*Q zWGmwweQsD6KlNg;@7@y^B8i1M{I$ROdu;-NYi6ry@P4R|Pn3YoEdifwMIJw6 zF(=H>${%)!$$3PLDlFXr;dBOf9Yj#_8qFPdYG-^gn-!mwpIviGA|?4J@l`80zA9K3 zzW+LKh)+=4`&ujeI-Mb#*Ffv{Gs^hLElR5z1EdtZwT@jCd}%fkZCau7skIYvB~+}1 zy2cZG8C&c7MuhgluulR_o1g=>Xay@aT0!kXT!~HI`0Vlx#0*#uHUHR4DHgjk=T@@v zVswuy&dqmKinS$iB~T$^0ZMBAPnn$NHj{=ypff{)nv+;7ddwB#u0a$fyb_|YWP!MX zH(R?AP)p?wKM%UkpPcH7dB-q|3%e3C4p0Ql;goZxZO0RcSXs78Yx66wCRd<<)ed=d znusD_iS^D=1dGcV&-6-sM%pG!BT#xQ@PstVRiUabZ!H-oftUU^02P$Wjl-oqvJGJpI9oI*H0c2Ca6dy0VNlhFX=1 zG&I^*xri0(fE?>?AM01Qi4~m|=+FcDk|T8>f#hJ8?4G9ZiSWiOEN zz@9KDLu^GE!L>!1Wzv?KOcZJD)(1FAhb)4_0tW}T4{%YhM{q;EGefyjp-JGVZ?v^@ z5ghte9NZpODyyXL-158IyJW%g7z0|GrH(}6+% zPcUfW#4s%Q`mjg%X$xwQ3WXzBD{sZw&oHQi^^{BphIT_?(YbF4 zA|@BU2)jn66mf-`q0qP2%P~DD(j^w4M7o&XR1_@~!6EOWzf7UH`pi(6-M4fQw3UmT&+El!#9-wrXn2c{xL##zcWIrex2p$Andi}x+xs1e(8fP`c{lk6@}7TnUPTQ>p({rN!yd_ zNXD99jF5KaDV&U?tN3+D63t)gH@D8gk@iv5Z5#1@8m%AMDBYHwkff{g^}Z;8%;@|h zn4q)UAD(`pP5(}esNSpfAr3N#Jxr#Ad&BunsMvKpqw^+Fs(MD}D0&K=+)5%2b-S5F z%?k&P>1uY%t>~P_T6DqeWOU=ST2)VKv6;6H_`1qndrk3tno?=`s7t+Ic{08>i`Jo@ zD5o;zQsI^4N_t#Nu5g@VCA@5V5<(HPNr^EcoS2`<1D6a-Mo0w@&#kZEWpUBK`eb-* zb|u2A53Ke_;Z?V}q**dNYIjbcbO<=m?$!Qekfz^>EO?CkjjZ3vgaKq%!DEM}1hgDb zIUmSsf-=Z9dlD(R0J7`#TN*{r&vYc2anMFwisEL2V^YZrh*i=-e(LV=Uruf4KCqKNrZcsKYTqtQ>)d-dR_o<*6Il|F6!K#|cY z;i-=a1}aZZ^h`1A96kNu!Re#loId&{e!p3m`rY9z3;>HWKv3%)9z0CGpL&s#JoWo+ zi^1_%e3HE*Wa>!>5h6{aVlf@ExOY>rSZ9f4+AAoeC`SBTh$Qc%T`g7M%hH3rts_)g z^qH7|-~pLgh}b}N^-YCzMLkiOYL>ahbx36Us%W0dGBw1nKZoK23Jz<5`6 z4vgm)0Xur~(r0HczPu@iy(759G~uy`v1p-I_I(f?`?XvNSt}&xjOMX*pp`|%5!%>y zvloKxir_3AF$J1d05C{^fzSvai zBOKCb0BXUw^=VWF4gx5eHBKLOXhh4qgPlI%)n?QI1nnY}YE^+=ECi)gbHgP=5${7$ z+p^9Go1;Jdv(uma{Pf+IPcC03Jm`cU{_@GSAN*T98`|*UAIY03mcC9Xx(TM1J+010 z#XpiQBJk06Y5ZwQeClWs(9V%|V|@DT%FWXkKM$w`*WSOWpn^Uq)~y)ds-}G10se6j z70rlF9bsdk?0dQf+xqO|x1L>okCZ`_!4Grp>^6)`19a0PL;Qp9b@wdJfur3%Pbmu$ri^vX41gk(B|j6aa_j zbZmVm*fH~p(87Nt6Jd!4K33E~*gtyqt6OKE-VP8NQyNL&txnpdy%XI-lby-e(~|Ng zM~?zvPyK#|@&N^G^n?-th6{}*wXi2O;BZrwahSEzTN@A=*?}tYl}=jWJ+A2J&LeCH z(%*;Lh3EVTPR8S}Uw!h?C&`oE+QNo$Nd`^vMN3~KsPw4^2`UeDC=#@jA4*j61rP@M z5uTX}C0d{yK^FRnBZQ!%N>UZ?1dh&`-qg4NxVjO?XlEv)f(rK&#!H@!uc8-$LFU4q8to62=+?27Y<$9oh13cSe{P<~4cb9(g^ojOY;T0dr5>tx z8Bt0Ukid1BowXoo^PB}|(6GAj)%C9i}g2^5>K#2W)B>{WeVt2vk`!9p|Kpy+#*==1$^5FWEV z^4Mb1FxFNPMOJbxF5SlR2mP&las+>gPCV>TU8)zk&B4fJG^a1h)!iqrT|eTcv(RT+ z&^3%6*@=l0)#CyWFXM}Zjln1IRGV*eO!7SNl(aBL@3B@Eh}S66b@1E(X6mU{-{ysi zWq31-hzjM#MeH8md)jhBHFX-cd6fc24e<(3hTm6eVE$gugsZO(0}bPO_VqhYKX^qt zV6R@*|Mrgl`ss5Vc|hEb07qnWcQHZk=eiYCvcL-RH$jKTXs|i0&I@ zi0amEb7*u0M7f56xi+hil-uM_-}qWmUTSs&+*G5OijKjZ8Mbk0p9l^;6bU6(J-B)) zw#ax2ZdimY{*%g!`cFZByJwe4sYS?b&X=wr<)JutGuVdZ$lySA(Rp1mqobq3dDCTd zFylJM=M{p5n{}j;g6{5o`CeqR3ah@jp88ls>TYCePeT9BSuXJyNPN1h^Tp@F|3xQ>{`eO z=|l***9gcqEQ_xBjzk+866^8_)%hwHp0Sy{EDZ^-Zmb~MZZGI@LX8awLms@S8@Yh3 zYe2Ye0f!G(l1mMU>gIR`*ZhQ``2Z-5T6@sC#)CbI+q}fFf^2d#37!w675Ol@*iylc zuiE&6D_E>+GT5`Y%`+S;fL%&ksyJ-n&)Il^D?oWqO6GeS21F+7`2HY+fYy`f>4 z5yq}Xu#lCvI95>VFoW9pyNGIN5@t|!EdiSrw|SCd85P3TqG|u~FZ1&all*;5rBg9I zpGB`ao4Udw#-8|yDZU;wUsd_RAd#_VFm;tZawMYcbu@Jirl9OWr}TKfJYwns4MBMp zBom7I9q!30K1T$1a`i2Zx9_VnDcNxRYYVft36W(9tVg?JOv5(BZC>wK!B&!xoFYpP zIaKvC57|}nEZJ`Ju*V8yxOCDW%g?!>=_5Fg`O$aCQgVJyMhHCpu>zVXc@hmu-ok_Q zoEOmYr4cbBV_?ABAS;kblhPDfdd>@|`g(IaF&b4pPll{OhLd>>vizJEQ23<}F%yj; z01t|+fQC%vj1!w$E6iF#J5E$v&~HqIAu!^}3wYcm-Bji`Ggbx!I$*j&y-CtAEDYV+ z?jpQ^K`7&!37usQb-J`c_t*dUU2Uj(=K%_PfA|0W-{1cWsUj_;NVXBg4L#c&KIhhy zGF2%cQ5Cj5hV*Q4@}#4TdmtBcBm#1%_gauMIg$#JIT9U&T}8=p7D5mk<{k{=OAyy?A6~2zGuHks} z#k%WTqd#}b71~hg&p?YYR)t1&&m&}Os5T6;6x)K%kLbEVR>ekj&l6;8u#KWDg?0gb zpQ5Y^jh+t=k*$F?3bPd21(bdYvnn(yeVuJx#cabkOR-%*^`|(iVx#KU!PWt`)30u? zj}Fk*Fb%JkO~5oRuoab*Q=MvE1#d-Muo=e%Q5vc*yETzjgGX7_sn%8SmLdW@1UTx_ zi0E8a%aS_NzJ`qgp~I|eux*D-;G~{!SuSZJNp+wQtnh&&x=K4AD$`;HIFILU9C;a6KXNh$K@s5XSw9!Z8kHB2m5mIg?h*(WD+v`E^`1_*t|Hv5vkPdA#tYD9*lyzM z8rxCf>457hQ1iYnP&K~pGOCHIYpCez>a6P;RI{!wFs*iVYnRv5)g>r%b#?T06{w&= z;8j-lsP2s`K8(8jJVpf!4b1+=cp=R$*Rz0tP{+JdVKY>nbyfvv0gxd(Dv zr)k$<+u1%k)x5Y4C!j0Tn*hWQlCGxn%4a!Fl@uC&SBj$?ti_09S<-S1*#x!5Rd3aAR{;syCmxHx^92;~p*+RiG zTjPLCmCgd9ucd?&KmZp;pJ6I=cs&E6PDQT=1~U7AFy;Hr*C$u7#{WI-^fPdQPEn0U z#^LAMr*5sBwacZ%BN-EwDW_=rn2;L&=9RNMU!Hz)@9DRI_e3^HF6G z8tI_`$K#I-hYFM#e9RhWs?9$}-9c`t#FB8kY?03>WJLv8_C>4r*@XpJ?U2TMukVuUI%x#TtGSF7}yd?886k0qU@SA~x2_KwWEiHIXm7!^qjry#3 z7^^2JZs9T%1~t6dSD^fYb8B-NTc%q6WEz|2`evcbDQ&^@CsW#-t)0a*!_LM+g6vPD zw>fh=3u{(&3nHOb8lQj!iGjYFH%Wk6lqp=7<75>|S5J#~+L z^4xzGS(MKIhNHa8EV{j|lR8>G1Sg1?V;cefcOPR%fQE5~v$Y;k(&d=<23=yZqf&m2 zo;LoL4or;zHXQC<0dq5eskX#n4)dEMm>!TPHel!0hEu;QVD6k;)#DyIqpTW}3ibWK zryjNFL)TwOPG{7jlH!AV%#tB$%(CI+?+S>}=-7M$DL2g-kqsR_AQ1t_+A^I~jq6D3 z@~y;*s?s+c`dxtxIvt)Ez(H4v{!!zE-UBRH!jcS(q-fCW4?R}x!mc_pqqaK5INnR0 zsc4#AyD?W_g=z;Z{J~XN$4Xv+0U+m{BmvLyPFtT*aD%6iF_ zC^slm6qVG~_1p}xk-WtktGRu!25%C2OPEHK@w3pj{wQEQWttg5i)9;`eO(3ia1B@p zZtuKgu+QzYHjm3~H`X)Lnyom74t)sF8lC%FO zSAJr%{|2#fK_UUG5265NSQ3yKBHa(3 zs$F_yT*<-3b3H1iTGGhYHeH10Et|i@{^-htKs9$YG9=CYZ+Mz^1*Dq>N#(#HwUKnd zW?M!Ex!$6YJ!Itknil7W~`2d@Enw>P=$*v1A~+**-Sx)I;exrqSJjdF&^ z0E}01SFqb#y$*YaH!N5Jm;jss=SDb#qY>t1+!f%UgRN|7Y*!vQNk^-T0|ORCr+ION z5em34qTAn$lbRUL`U>&5A(!kXI1{{NZWUR87p0>bh+=FLEHkz`I=skwfCUAd$*2B` zJuaRu*w&S>+_Gj^RJuHNyY#An1*Mx|A#qg1)f-uYq%Kpdt9aS7z2Rxw6|kUq71q<= zKR~H7zTmin#r$SFI3XiqVam=!yG$_CzPdPc1+rTxd=4(~(jl4aWl`$6iI6Bh3qV;M zmJw1DL&Vn!!0n@FZ{L4*?~C}5F_7bH#$Sse70rtu@>S*?m8sYaiIE(y+pZwm9b4HK zA+@F;LYfgpkJU07S^?g#U4iEA(UTuvo)9Q1|A>R1XV7$&K6_O*yhpnN&0F?diOEy2 z6=aTZ%6o|#oxE39*Rw}u!!xuiFx~SNi+^~8S+Ca3@IhC?Y4FXEtE=95j$^|kv`f(R zP$?fTk*n3}@eTWM%2;dSC62XjczkvR9H{lzUw!(JcV;R4Vv9>jFXG_m8jh$DoH!Dt znJ1&7R_CqR6{v0)PemI8VIA0*!ct9cai^=$@w;d*+VHsS3Pd<@-QgpM>;TiuyLHw0 z4%N6$`YxJdu+c5k0f|E9JpYf{u4T8b>&X5}BR~TQU{&(hdm=;pa(t4 zq`T)NW!ZXJPwN%QFUhtPJC+p7wj^7Y|D~x*iX;C*uc}qGckRbH=km&d80Lauc-E<1 zyWVT>pw&UO2hez^?g}WcZ60jh+5Z8D1-5ny+c%t~dI_pn*(Q_^5*t9(HS05m@Vp*(1ynb%3DGIJ65KF0aIIwp7|Y0{ zB%!raj|*m9V~(GSRnQx9rFM%7GY|fL;N=_)KURk$LXnT41vG9nTb2N>-Sz^G2f}F9 zORqDYCAvWhaTm&lPWGHSvpoM-nevm0%|(%n0g6qIxbgV;b%aWo&H!rd8cL)YCL!Hq zmWXIFNgeJYLLdLD?SrjvwE|)=CMH(kZ1Bahww6Fb@6ruriGqfbRN*d)xgCzpf>AEE z8KAbp5T%pP$d*|0k7AZ`Q6{mSNa3^0a`sY<9AuC=W0teRc~}BBBK% zLAZ+uM=4@xZcv|N>o%?!KacCPZ}t)qH#^zET}1eGIm=UK9>S3Uwqmg&+QS3q`8xiy zcuiWKs@i#jrJ85~$ZDb_2KNE!o*w{&CCJXAMFbe4+DnM( z5wStOZy$^vHZiQs=*HA;E3Vk6Hm7=LDIi)Q5`Fua-sqTTzq@QmHY4^qU0zn=V30;u zZy(2-;|AKtWCtK+)k*7>Ep(ofjlR^Hp=9&+F@bO)qYd@3vtT#qDVI;9YMW^c_<~M( zbwt4_At77FbofOnFIDQ?57A_G1$qD%d%y#{DDQTW6vXIKF$IjTi_|Ke3RUoKbZJFPa&r3+g;F;GQIlMk;8>E| zDp>&NsctDlnp56Yiz9R~DDbuClp53_HF){Mqvf+JyW4m6&i!QFceXMRD~v^Uuj4W_ z(Rjo;b)cDU2f*o9tfk~@eKvS;Z3PBfPot3g9B-zrFd&lS zxWv^Pa{uZn$mCr$+}elriQ-ysY|Ft)jsV50U8Oj<>3BD71qElQ<3RsrZKx@P zGhXMC!lYCmY;hpPtSV`MxP(J5#|vsJI0!3MGPqkAMca_H5lYTVvQKfDWg`-eU3`Ic&7v9_qgKFd22+pLaY=KWmA-n=PzZmct8MLtZ{x$wV{Qab+1TS5+Ii)Rhzw6==+? zU?n9hv=y@JiR6g=Z1Sokmdi7d#Cr6v!ED)$%qeWdV<{}zvI!6M6!s8tMk}7Mv+UNVeCVq zY(ix6DNXOe7VnkfLU|4w#1TGLMn*!Q3?v0T1rVBFQe+bl7Yz(>K^im+4G7q}f<$Q`CR)J)Au0>do7=z<;_Io*tD;~})j-1x|;Tu-h!F^{+bON75 zm{_GF_C&4*LU_XX#n&;t_)v?L!0x6jr*Q=&nljkr=z2^Zs0;c|6w%9K?nlN{^J%o)0EG!0Pz$L+ZqoIR`Lm*iKUGv3b-#5rr ztrtbkWEn}*xLbzKWl?OdB4SQOm7G3HSEEJPgl%&&I z46vf%@zK|}C#p<8ziT#aScS~edwK&nhQl!yP6E{fY}4h#^SB8d8`PZ4rru`uOUk*9 zfF~t4En?lv{Zd#lC;rKPk)uQMcJZT3@OL!rGx{Wr2uY?Z0Yheb>H{?N2H?fCv{4(P zdovR!1*ET>=cN@uGbtL)t^^PrNA%P55=H2HP5i%RH0 zdGR!vJ-Fo$qw{;`zIgHD*@LY+3Sx$#e%-UfYnS%6RETAjlVGe9qjwNunm^w7JR_&H z17YMQNeG2_Mi&cK9A5w;W;Yt%yimC4GUL&O!u^@p;kf@=I8KE~GM?t@$b@Hfk%sY@ z#i`_@89d%|08cm24{OzwqvAgEwZeAPZ5HgGC#e-0wZk*=LVMEr;1Nt86c?AI!Wsn} zF;{IiRFa98dBT1-Lsa2VJ3R9nTZI6PxBx5_e1}E41=D2qCuy-@!QJx0Dj&B!ULD8p zX}BQA&NMo_(EBKhfrc5`oFMQGqQMCTe@W*kIhq*tHsAXomW(0Yyx~)SH4`f3{(FC4-a4=I0+>2E z>FNFE%O~e?!D}8F(wQOgg&9Y*fH6KMw^x2wbg$m>i^Wm7JKi{G8gI;`;MoU)g}e0- z*VuD{3EGeUJA!XnurVNsT$)^X0mZgJ3B}e7JEadr4xY@@Fab(TS%zhzDT7!63DlEN zyz){A#a0KwaUY4CGa2TCOd>IJA&jWSbPe!g5_gzA4vXX2OM!zf6CxbIvDn-{{lne~ zM5z~h{XxTJcDa>|>x_gG{x1o})0YAey{0f0`+~vvLI!jmXOff^O#@_m>5zatk0}7r zeF{af4+uvFXF$)-4ge}@L@g|r4gWhy$TOP)kR~=WAtd$z;hCwDvp`GGZSNjtF)*gUeNvi&lo?g`xtEwoPV;1qR^2E+ssJt5p<^R>hNQ;Smo@ z;WHs1_QivGxZ~NodN&bpgYAJWQ9DQ+VZ-wWWyPd6$*Ck>fTBqj!r=fY6q9Vx0IIlx z%M@s$CeCOXW~Fb7Qdzt}Me8g)!vR$An;F&e;*=feBfJBomOu_+3ZnI$k^w60I zrm)m?2%T09MWzm#p1tPvu`?kM4xn*^4Kx>UW&67?pJUyAiI^sgbyo1MUrWPYg^*~q zg+Mp}%gy$n%lGr%52-~xlg(o-X2Kinqd@`t;OZHv%f0{Q{)P8O47VR(emGRAmK|lp zQua(P9vwh+v_ds#zhQ2JiE6R+cU&rR7pQ2Pq|7;hN>)tIrICUI4+Or&!?KDwJu_6! zv}{4~Cj-!WK^qIrGvNz5wC|N2fGQBq6uWJKH9{qcmiEq`J-BvVo3ZGa-#WT?|9gyo zi{9hN5V@zMXy(;h(V|=YPCnl!g_Oxuq2qurjC9C62m72eArMZ&yNtg5NnGQ68fu3j z6V~@4w$WNh+L3+{LyWvrQ>z3w)0X{A2!)gAKHPj+&3w4Xn$k#IR)YoTUK`2LZyIkb zQPGwPqi`D3JI02wsf(*99h=hrw377G(8?lrNe!0*n(u>P^9PZA*t2D}ny@@M!E53gRA{}CEZ#_= z3GczhmY9zvHe1nP_y@5GU#&?X3>)mv2sXA3fl#E?N9VKjB|2s|!Z#e(H>@d6dC(q3 zP_dF|7*(I2tJRdn%=I{M3pq?%B4RcnjKeWRvtyeF=k98F^C$vZD~|x-tAm6zuo>D* zs3xD+@}%4||KiD+7e9Znd;GST_~T!1?p^x)U~5Y-EZ(xi-qGqmY7N#_CVl(vbvgVI zt&rJ8tMoleEgXYz@=I8DaoBgy1R$|oQSX19W|WCW8+2A}ZVPq}$0{cbhutc=1 zeI)>y41Y<7g9&1gm-kc}(ksQnAefhu4&w;HCRe5ie0wW1j3d=5isj+n=0&8#IJAU= zLM~=dvAb{o+n>GQwYznJl_s#S_Rl}x-9fT`=HS}WSGl1T|`CNEzJ0~G#lp>-}FGW5cY#fD@B_RPJ2SUdgtf3|Etry=o zwvTDl$Wa`7g`f2mfFDJqe9oc0!d&Ej>P~(I3`6M@cEU1v$ zI07CWx8o1ku@HW$%+O=N+U36h_=7M1mcX->QX(GJpMHm!t9*&k7^1KDxlj}J-YPXD zOOC+L2TnF`@MiS}@v89QH}LvSHaXE&CZ8^=6;(f1c8a&43nRgD>@8gNk6)KPEK{Sp zfsbD`?LSs)|E-7nAG`}S5|(jcFufEsfFC;mU$9Qnr-IGg2o&R72P)oyevg#Qv3H0T z2%qh2VtawN@B(Uq&g|}-lHmaDwC-KGgRrW4fmq$I-@@dNMG_k%F8~V^f_IOqiaktf z;EWv3u{S8}j`B;9EB;08QR>qisaE2})4%U&@=?7-Yz_pcWuYOA5WeyrWrWlT@pd-3ySwpy$A7;Ey<154D6wUD#^<5HyhRPh${ zzNCzfy+u?eEi8z6dJy_$8?I39(oTJVn`~&G_D)^iyL7z!I*ng0b~o^gjb)aPuDBcn znU>9CYE`;@e#PTlZDQ{dD^(*<+-lK)Frmr^!|wdzG*@ z$ItB^hZp$lKmY5`|Lw{po;q#y<>PxuhIhBWTK@gxy{)_LZf_iR*euD~;$X6}x#h$&7a!xkwCW>l(pXe%&}6W-$l8FF z2|67vgVy-GXqH;39g7Rv^?6nmBJFhw8pd+3|G>f{_-@T0f?*u@Xg&!g1je{e6Q{D2 z9K9JCuv73rI=J-^x5Dn<{b_loNUa;^6vfuxn(6R4!^3EqW4bf)O{egHSScwM+!6*z z^?>1>heu_CRE20G-o78iN{`h;n04z$l~IKp2BZtROTp1dlZ-kAR0#An(2d{kfAq!v zM>0$l(rg{HT@Pm04s+giMtbTXOtcPeO4=l%M9zfIlERvmfeB4jB|jT38@%8pJPOgC zo%n2-pYWpwyM~zO!_CM{9YjYQGn~VG{H$zUO?@os*}nhc$CJww$_Db49?g9+>sTUh zfXwBlR5~pdnuo^CczNs;M!8X_@5maMk^<)nT^6XjGt)=)w@emXmgyL|5+}_wd8;Tt z(2ZhaV49KbS7gS|EH==&mGh4d&f(UFpKRG z4WTb)sx1a|EG&}aI)x66c0E=qpospVgMx~s%^)4Hj+1I$U<_dBAV_na0wcT*+&}@< zlcCW7Df)*7lIs+thpxd4E;TmJQuQ!kV)8l7wA5+G6A5m<&O&NM!zBCT@C-jf`6 zU}nR|;<0^_%Cl5R7VH$fH#hgTE}?vbS&e2PEwN|>#vi!36>!arM5+J=SoB!DGPyitUQ-kqL3WEvb~pR$gfy}SQ_)6Dxs{M zpslpKgJamPAp;PP09Hlm5rRE)Ez~-$RvyZ@DLMl~C= zn>@#*Y22YiOXrbzsVkzz+)r>Ly6isbllOL5d^B`!~P) zgY26HL0U-I!sAZ{Kzb|~KyrHvPj*EpqmNtyf8O1`3nt?7 zS_++mPuR(=E| zcjW)QcZn7xZp|VzTp6p!ghAIB6ACAH80%Ydu+7WIAK_kvM#-91ZUkd#2f!v5R_K1J zSY+8T0+g#?2l1pq`lFjKo_vPG?5e;jY=Kd?RiUfhR&LfNbQTH<6?Yh&JB5u+?u21< zh>t>jA_|V5`x=+quNjv{d|>X4;c_qF$29~oO@wo-cls*U7A%EyS*sY&fkQrqN2X~9 z?Yu;MH$O(w#-Z)ygXjC-TrFK_8`VV#Ryn1*+`*e<|UA22747fJSUt$JeO7h%RJ z#z<0{GoJTnmrmee|P7;(wVolTZ7UPZ|lSi+M~^C z0u=Wf+o!<>X&gkNZ~qXUNs3?jKEahTk8ck<;ACtf@`jX}Nrd-r9-9cR! z?g_4(0(j(rzY}qp)L_uHUAcnP(xkYoqzP+GqY7Dx)eM*{dZ;j&a>J;vj7bVgs4|?P z&;qhpN~(1%?K&-uOQ02$-J*#Kn#Njl<^4)Rfk8496R_p6_b~@??<=%(aR#93_%YpU zX0z)@yTxYMDVt3s6CQ63zavMm=an2Cvl4&2WhkmAlK>#tX%TA6*ddZy@%EzwE zB@8MJ+`#YH#6I{41v6N{Gi(e2p}~{<8r%J$se4 zmlFZx_rg1;m51E(b2XHl>%F8aTU_KtVsZ7rrt)-+uxVWj4`p%X{HN6|Zo<(hLcp=E z#g(3t-c|pNt$tv0;pOGR@lhq{hb<|2741jlQm#H?<@iXgMb*7+dv^26(T-F0*iTxr zHLoA+ld1or1Yzp0HWPdrdvBDN+TOo`XncPQ0x}W{)MD`Dv&29fskeRNFcZgscJ+lP zIF)t(e6VJPbD_S*2Oryfc~@?o-~rDP^KM@Y?{0Zs*wrhnv!{9G^EK0(>t&j{&Q`>F zYjf|`*8cZ9yF1@Xpu6)VS1C;dqbXh#K#;v>b|nN&?&f-Brb0lJuaj;#yayq+l*p_h zK9|gnf=R_);IdPR71#VVKm5ACHYrN65F9|^7krJQry0b>w$K6 z2*fAJB($k|Ky!G()xc;^3s_OFmi1W}o(W$Y5&6#Y6T9#yA|AR}VX)T1*?;%5y(^%~ z)glTo8oe!dPZvh=vIPt(tuR$<7%c!8#TP&7*o#S{(bg4UB%fQrpt=erbqK}9C|wTiw)QZ z0I%ei3jp-Xa${u;;Kk49aGM9MX?S$>$-!Px0#O%(KTIyUcr;zITuWI$8f`A=bp7}x z{s$Bd!qZ>L8y8QfHx^3h#M31ObBi!qQSHBcDx?+`fwk1R@IULv+kn^3FV;kGW3)!r zk&30|vsvX5#%J?vl-tnUm-+jz!fTILNC)0KB^y!fRvQ(nXZ`GS21|%S=@-Upot|*w zx47wZ*&$Uq%lD@l6n8t% z{fC-#E1BnSOuC)dtj-!ik8>-c7!IWl$+@*oE%u*)2tPCXrxZ9A{|k~Q{MCEE&BtpXlmmP3(k%}z0Fs5z1i z?%$AWJA3U_qK-o6E$XQGkv00?m}(2_I2ya8T7|4tWR#@vrF;p7FEu~1c?nnhS7q7) ze&g$z)M$=eK$h11^y<3jGKT>woo)K*`1zA zmTN#iS(71~7bq;wtMF{`68I|1H^m~r{NO7u!m2#mdCh{Hc!yV`+2S3V{57nF$17So zKLpKoUeh3(mx!A5o3U*1HjOtf76kzhzEDKcogr<>)mXOknmO6Lk8f@v6rt2qQHBpq zvc;Q>d*}8|P%!X=q`cx?ZV_hIP)bx&hhw-HU%Vp67BA7b>7ZQ6%vu;)SX$4}%U58A zUK28_#jrNn3KUxaY}|3wmwxz`ZX+$R(Gk~-%o3Z`)AgE~x}Rnl#6OB*JFf|uU1Y-q zTjVyQPP3(o7TjMiixx5?!FFDAF}uiy`L#ec?hlhgF-2RMUpwcmq(hstW5Lb&5g3iG2ZV+~<}ItL4$&Ne*z*@CsHE%ExL0{U$;UT|1 z`#3^B*t+9vVS<*7aO9Z5G1Ojw4yajWB(HN#)R#Ab7e{|KNX1%LNQ0>D!u2fPY}!*P zG5}?MMTX~^KtI&ONl)GJ3G7N6u-!7;0F*k>$Th(Al4XEPH$>*=ns9L{D9%#NaE&MU z>$6LZq%r)k3Utk)s@}Et00VY50Lcqo6Z=)i-i$2z-jm!XDThish&%~fD+q!(>^<)r z2E?pfk|(+*Vw`eHX)8M`jWRpjHyXGdQvrwKeb#{29d5wOOo-R6u8H@$>^jhEbne7& zq&B-2lb#6EG#L>U+6pk*jAZa%ZjaL)!vQN(9^TBlCe|B{6&I+c+p`U8biy6101CK< z6{#a4=mge>iUL^XK;-7G2^ME&Ul2RY`63?cAAH7JRD__c;bYQU4I2uzOgKnfw49Z zQ8~nSs9K1e0lHL-#6StC>F3DNT_5%3j5?2>nIfa6SVIoNHqA&DVi7D_YRU$@ba!O# zu8-F}o{XiP+S)t2O@1mgB^{HB04iuVYKrwsM7euZEDD2=NPan~O6vr9pYoiP-t}MN z3g)GL;lxP=*4$nY^B`C#NrAR?d%Qqm&O2=f(2j?kzdpMPR?ux7@F5+OXLan9y0T`v)|gnkb@0kh%*kU@``LVb-~CK>6eYeKHuqqRU>l7)h1vLWKG zLsheNf4qfa&UDxdA}B@p$n(J2%3K3`7lQk9bYAXI?lvGX0Et>@#l)N@|8yCzVa^?tz&oJ19kWX<()kEu`_$!bW^ z0?w5GlrNF*KNlA9+PVwC?)-Z4MXXR8vjAB9qMSV@g(XezvLdAWK(ZoOrI5Git}oxq zg)Q@u&x=8SQLqc3qdntin&4xSMQbA=8Q^q9LI6!K!W(wi2mN6ebWEgC!dn@%_-hIN z-EpKkUck-lNm#$Hq8mt{+Iz=r?~RxE!q$V71J>4UScOlo<6YMVLywU#wd4}VI^I(f zEq+}BH>oa{aNAUgmto^h)mIa(xD2o_F(E9MibwSw^%P~tMW(lcY+FacswD0ve>Jez z0e1NAa~E<so{v`Zs=Ve$ZD; zpc85EYKM?%TTz>7rm;{W@Ot)OhJH>@rW;d8M`h|$3qhvp9Tsx=@4@>Yyu5Q_dH>|g zZyq1qJGZ>^H7frybG-ElE7pmMgY`ajhvn3CUn^qOLtN49L#g6)eHVE>6@N$=6vZ{; z3*5AD2!I=#dp}*om^Sh=)=eE<)1nKT+^CW0l{vbA&DPkl8Bx)eS;m#W_<4Ej6RlAr ztwQ*Z*dJV3Emm)B?Vb4ux6H-cwHKHSlgpe(J99MU{yoXbSMlf@Nl08pZ2OgfKqkA} zpG6L_!x`N^G?xYq~}w7RyyQcg%@ z|HNHT@9V?%49jTerH>_*crq52coxDc9#<2)LgV2PD;37RRjaXbo(sgG+CYn<%P%523k~0tkFy1MSj}H#I)lt&=p!ZV%!ti@OV-v?u zy#eJdHCIn^a?czL%a~{oit+ZTEEv@?8ITpnr#d6_CrT46gV<-?z>m*xr;tIw-0n?|+{53f3{MV@Ji zbs@CoL!i;aTaX;%E@xWgWEBols|+#D~9#W+1(m>8CeCRi@- z9T(7xC*Ra(#yOq|D=c{MD{_8{)RrS}f7Tjf{73{OIr=!D;N3A`cQxwHgjH5*8V6cDv ztP8h+a-4z9r_cr})tL!Pi9+s_wH@S-9+iTXa^_MDDIcDSz4_rR_AZp*+<*8@_ssTi z_|nns%NL)U{9)Mr)&s8{goo1|DWQ4f;mCN)sLHADT!`@z&%SR=SEmFpwhYaz@xOM&m2P!x9VXbb7lzy602Eu`!o(UCyB@3u-Xn@f#2;^(4eRAKVUb%}|-R!G-Vm{6cbF};Jy{A|P#KUW>M3Y9zD z5*2+eFW?>5!)u#%wP6QX^tTZaKd1GRf~hWGFrV&rOIY;0yp(qw)^)L_mTK4rFrUkZ z*CEm<7*BgjFm$`Tl6M@84z%nZ|3ql?T9JJ5WNYv0DQ?#hBz#W-!^DboXl|!r zgow@D^bodYW9EP%4Xc#U=qB+0o7|XKURXG3boHT4*)eS&;Xzq$0bJLTVO~W78gjxX z<%aHI&^>A$8=p~tmdW(Dhqqr4YH+Jc?Z~-Cp3ju>V@kt|cPAlAH!Bx-hP)YKs^rd! zqFeyYkFSrovvg8_iPvn)3eExRJ>JkVgX7evA)R!gwmq#>vnwH*#aY2u0OAdZRF3S7 zGP5v00t#uO;}Ls|e-gj@5_^ze;$fUmviH~$Z{TF%ohNdTC+^7Ec^D80IR=j#sY9js zS|A0jOVLvSi8s$Jgv0$?bW{-Cjs?NVX~5Q5Vw898Xh%iJ+N%Q`=%`36QibSYH4PjD z?C1hE%TBr-6`^9sMikEX-9K{!z7(gc{Znj7Nu;dxJRJgg_%7Wo{wB^RkqLtlY#5lr z7otsx-6VO*p2CktP;5A-rRequ!+H>#T-5jT?Uz4bsvH-M!RzZlMQY_wK z5URt#Wf-4o1L}d$Bjm<}lP$W`(u4f4Cys1LBYZ;Te2RZ*%x=*$@UqQ-E& zj*qkW965mi7GKzgV$$`D z-HaiH*aZe2ytNSSZwG_uiJaw$iC7r)uXeZ3?&{_30xh+z9h!yMwboiN=mtrnS3uhc zrGhm2T|%Q35b|#a+VaFbT=$J5?yY2)u9I{)6YN#SG+y4#WeW^IvmaSvvF(JSPzOs{ zF-uMCux{fNM$wx5&o_4Ja1}_b$tBd@4w9D_em=PQ_#o90O&D6890~C9!?RsUEv@qL z%ogQd*Usbjcg`LB<<|0!R%`s(qk0ja9G>kNSNUvH2*loq?pQg9#x=$GdjN#z zR3!*nedIP%Ab9Mpb;upZgjPl*Ne{Xj$*Z^)!UCQWMryY@h{g@BAPlcjmBL_+O4z;= zAb5H1^7B(Kf7C%qhtl}F6`*w8i*utjdo&d$~T@s5(^)e`WK?B3;DEXo}R`2`p$(?VbA~0GZe>v|Bv# zFBklsD_Y^;h9Mhw0!KSyeQpkiBR5`W-hY@4qFGrMe zHnR+Y4<|4lhHC=~_Ol}mJBpZBeDe^$g_39G5Mr>Yp&nN@abx2CC*L3dVX`j4+4BAk zS&_w?DBHi%x&HVQayLqu3Me18i-1uWE&@nxOa4TPE{+DNWF?chWzuYMx@RFPtGn3} wl-046=vshUZ0?=DvUeE=pr6)?>6MLK&rM*)w8XMsrN+16h*`A?jA+UK16cDLF8}}l literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/MonsterAttack.csv b/titles/sao/data/1/MonsterAttack.csv new file mode 100644 index 0000000000000000000000000000000000000000..06f8838dc37dbd75f5db706f267edd22fefc260d GIT binary patch literal 76672 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

    hD zj(y8Wip`dy1FuCcLhj@$`(%-f#Y8B6*$?tdSBJhN(~s ztX1*776~a06~i2BUojJmmLghD%{$>hyf_n&GKV?*l}}Rt7WBS$W!kO>Yl%@ZWvJow z^9c|KvGWr6O%4k|`X-yFbPzH-Bnu%sENhnU3W582?vyjO*b5Vrcv4%a7GI(tSACKg$a^YR3E5^prpONmyzupD!&{F!*3g-x3CjLc*7KC(HcJtsHlqjD+EzUdHz~usnjF56H^?Fb*Pe(u-=qxg zx%8vH1s6RgX%{%>M8aIWMS>CAGKV1w$DL7LpCMWbJFVt(9qTEk*=0Rlan+7!d&?X$pa49RH3YVgayi3>I7KSO-U8z1AP(Q zw3RVb!%7hE!WBR_7e24*gmLT*xS^zNCJoD>I-;#@-5XLORkzeOJBlQ$2QQ+(wlX0c zLxNC73UQ%TMg7fzsS_TjjwF$Jjv!-9g`piFBk8sZE5wd~V-RJ4yZ2L^daDtTS~Hl8 zolpTn-2%yC{3W`HV1}jjM^4*|N1|CGX~??9?h@E&c8CIta&SCRt(8cpIE7mG_2-~= zQpzMKm`$|DW6H)uGC%T~U$|yP)kKgoppj7>D~z^x$~p+2c=2Z}u&`tWB^f*kJb~tr zPM88z(^W#(&q)O!SwW^81Cs~T83h1yX$7bTJ9PyhkKq|}xTxsS!)GUVpN$uVC|%il z8Ro&QuSPoUedk<{e)a~Bi@QI=3RVSHXLJC`KGA_H*+zJ$t_hOw<0!fCB3C$U3-VT% z<}Sf9I29Yr2x*OnIFj;oO931NGQcZuHzZRn{SP>27in=(uSjNCpbt{Gh;D}zc0btj zVP+V7Rl+@Mgo=iI3-Z7g;gZAfnjXAvH7E|!mAm1TvA(utfQ?b~bir;LJ{4^oISD^iNHT&DI)?`ld2>h8P| zA&QI6EmJF01Dft1NNGZ8PTSr&n`J`=sO=XyTe{NbNS=*DLU8<(;A+`_d({BW1d)dJ zGjfwuM#R(6&cJm=maJ4&!}UhY%5Btc6VajVj+-hH7vdPODpE!`Q)ti*BOAA@6)CF> zXf4gPc;?Ah%B-qRA)I{370|=RcSm$q4FfsM_GdH$Ag3LBR<$77vjTI>4(3CwwcQpW zGSqs?E@ET@(NBZZlaJUHp^}uwLhOo+8E}n-*jJArmC(O(`6knZH;1@ucz0>@&V#L+ zr-o~{pFLc8_Vnb&&AZC9x-x>zlOZ3aIM0ee3%Zvk*lsZgw%R&=a&zrygf>^OEwn?g z4VU05uCR~$Oo={Z;M?RR=3gFK^@>0jT|^Fk~knE8kb>9%O|o5 z9S!S}dC_s$7zbPG{Ka248}@L^c#rXLuXcy0zZ+ior`)7+05^Cqg?r<~C8$G{$W>o# zEOsUAiHe+jh@=8;)rc#Z(vVhHwQy$IdefLEcq}>FL-HW`kZ~Ztr~M1_O!9~RFy$Ll ziu$Sg*-ks`^W?%9EE8jw=)~RsJ;<7w$g$w3u~g+vV%;hnA$;Esr!ttVrN8?94f2MgOdvliT< zL180`O4}pPgjq%y4TxTT%otsETT7T59Upj!yeH?)`b{QELyu5s9xnihO;pn_W9@NU z^h}~|ZxL>6gPJH(dj-W);KK5DzUM2a*HWjr-x7M2+vm7029FWd%(M%`pw5-s@;+yf zl;TZ3C+3ar{ybVfAM}nmqB+dZ(l?hE(QtqvdwiMB9y%s<#--J$9hW^R7A7C1JhVhW z*SR9jhsYRAWHQ`QdQGy`z#ZQKk}mqFR5J}`eM(izZP}A?DS;C=i(UlT>k28=hi%V^_n6iV ziKqy+->qGJvzYz&JrZdJ=5}{$WuRY<7&~4PL0g)s?sF)LJP`c_H6$A&1%uPHiby7{ zb412n*vb`sBz20^OhRYE4{PlYKKjGpEf%83T#vQOLGS+Nuj@e%T|MziH2&${M)B3$ zlpaGSWHSmE((0Pv-P;DzX!SDO-w)=0<87c{hFBJ^nIvp7j)B{|A~b90RQY0@-y6kF^dLRfsca zRC*sB#?mJbmk1etDT2DLLL5pn9(Jcyh!bnZTZfY>#27g`0jhqByu>l*^~WStUBAr% z?zZxnWLvTbCpCu|XTr)}4{IrN$>; z4bG{7f1C2Rg+H35qu$g@yf(WtlD8Eub=V7~vh-+&Dnq_anOj57P!-)F@!PXIBWg=m z=X_4sk#?3sz}u9tHNd1y^dh39j9-N>(e8}cZM7qC0FN{$)?39k_rlsL$e!?5z@A9Y ztWb;6Wlm>aBk?<=&uX~tJWJwt$e|UG+fsEXxYsnvELVs1*UPDgK2iq}>fqArB)$yq z2?k?=cH;(_&nZ4tnCohVEhC}E_^6pO2+@pT9mb%Ul#JLM_# z{*2Hh3K+uc@xAC55wo=mZaxS5RAH?pSBqGgpsl#Y>!_HStE~s!6{#~-i<@}JD`Sun ze2rx~9k(}sIT{ID1;3%YF;|BWZ8bs6Ff%(QY;a5S6(?YG@{inmER*);Z)ZlQzRC4G z*r<`yt8FH|SmAuaoh8^^99uq#V{nOd>21@#?GUGH*?#utzbR8dN6ebKQ;p zDGfIqWi_qJ_AEod67d3T@Al}*53b-P2?DR0XM@L55xe5c`=%~;<13hkLDp76YieF- zWh<9AO9HHdZQ8hJ5AP&I9_h*CjT~993j4;=E!C;3Q7dt$g`P3a_G;T|v=E9(nAZl6 z1HD>TO(hZIY_++L?&ID=+^s~uV^a>E?X}GP-=^Ss6mOx&053+=Z zc;lf^K?pgteJ+TYR9J%dTo-TEZ9ciKRGFT1xi;gc(X?`l2a)$#N^#>LCZWTdR?uSu zk~)b#pL13!ZnH&3#u6omJSN7JM&U~^P%UjoKuMwKGtQG?yJb3n{MHMwmj#h|MfDfJ z4JcaxDzzGRF!+b0)Fd2M!}^vqed^f8 zbPUNrlaad&EgDa0!UZ_C0rO-~^dljErY;@B1%T=x6~7`~!SelZfO9LHb@lA&>hS5R z1VHgU26-u7K0AJc9UUfHTX;*q0}YE88lsRi#CPyS&iG(P_Er1an@LXYdfH(7)1++53TZ8#(5(| znVHEb3`Z47L@$$Pk$Ub%F1HwPNl%y`3R%Yeu;y{MIhiO64_6e9-`ZF@rd-Imk4qs7c7l#O! zm^U8uu04BrBIu(n;i5KAp*xV{6(rYTDYeyZkyO*`h)|a-2sU=((qlOXV`r*mLu@UC z(~G1R6;#C=c;Xj>xm2qSSPb7!v}x%r;&TSZ5|mcatWJ>4ILW9E{A9C;KGg|5nk0PQ zH;Ii8D*?z(|0Fh46PhMwUyKkfQPy%QLliqWqB5468QL<^s74|TgS@dC^|Hb6UVr1< zO`LC)HqgdY{^S;zR#m>KzLpe+t)Nf_9o1M2OlsL;^v0x(YjQ2NatRP29F3oY!IzwE zdoLKkYJw-AP>;I70F_BO;jsa#Qm46wZuwK|QoFI!o*suC^?wpeBQzXUWi}t4#&jk% zqiO5ZXPU#pqiQ0`99YqQOJIeBGhi`-<s`{xr0^3aIIhh0fB zDMM=*C0Co0p~4tbo@_IfG73V?y^i0mx5*xU0p;B$a_}tO7j1JC5MnbK)?Q~7vC?OJR8!~Ut+1H^@EL5@U&QF%gW)yONsUEV z+X=>>cnTG*69^E(+oD}2U?fP0yI2^NMwU?@TlJ?BKD9``p}w>#dq$iY*fG6`Xw44; zp{X6#8phi`W63PS9&%cD!&h2U`;S|X`C2TN+m?IDNk1?SDFrC(51$T5mMrr?LYVp) zf^Tz7TLoHb>`uGBoZFL?=;+RqXOFLqF7;zq-HjAfo5f1lG3K1&e8CsUn-$*^K>mIT z%F3kcU79K|p6dR(M%~4wf%`g1thxk}55bLL4D1CMZN_jFxY6CPd~)mXj|OfSv4kBn ztXLZsAvCxaag*ZIymVagyUP0NMK+y4_DhM>Btuc~#j5N}|F?w4q=j$bH?6R}gs>XU zv#kY4MqnLN{Z06M%X}Z=h0BO)#qfaV=K9Y|1G*`q2|b@A{zt4i3Y4F!I7S) zIIf`W9bz@OrwFg^9pf-_8NV+LZc#|7^~031sU8+lv+E}xb@}8q2P95LEy51}zowK? zRS50MPWvhyhLfraukLcFD|qNCydVbf>l;#g6~N3Y6^K@8hp8795wqXtFdOaGA;;8* rsi^WZ^|ks}>hF!l%P+M%B)0Is@+zF&;SXPp4&SKr-_xdE{(Juq`r3A~ literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/NavigatorChara.csv b/titles/sao/data/1/NavigatorChara.csv new file mode 100644 index 0000000000000000000000000000000000000000..e1a42acd989bd2ed6812b6493938e1372614a720 GIT binary patch literal 103 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 PWK%#!BAEiz1~UZ!a;QvV literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/PlayCampaignRewards.csv b/titles/sao/data/1/PlayCampaignRewards.csv new file mode 100644 index 0000000000000000000000000000000000000000..627fbeefca4f60425ab2cba9b2a18a06b26f6dbc GIT binary patch literal 451 zcmZvWO$x#=5JvYx?;x%NCTTrES8hbKxDU;sg~rsFAl=%HH}Def+xjMXvT>qj%SLJOy{uW!Ga)nRSLcjTYYWyS~cB`tC(PnX^89WZ&zu}82O5W F#UEsAqZ$AJ literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/PlayCampaigns.csv b/titles/sao/data/1/PlayCampaigns.csv new file mode 100644 index 0000000000000000000000000000000000000000..96ec39c3020b2af54d8770e7890a8376f09f0047 GIT binary patch literal 243 zcmdPbR|v>StaMJyElA8v&+|;t3CS$UN!5V~gj5!!>i8F=<^`7|7L~XpmOyx}c`0Ca zKz?yaW?ni%5-I?b_e(7=2Ab)Sno|Iz98;VU^YT)QJX3hN40V(qH&1`uyykJ!lE+PJ zA2%<4+`Rd5^Q_0sd%%pg$4xsQH+LxM80jb(85kMp8yEtS5|{&G0g-}{v6ZPM5J5x< ODKSveG0-vK_2WqVhX9?C7-yOyRs$(72-4?Si7P?|oDl&ifh_I9T1hl{;k zNyOe(d#l>JKetq|C+oF(Q@iY0Qt_Lczb^^p`o8=7nySC={=TW9`hHxgp|2-2*)wl= z9;v&r)8Eh3hrOTvex>1lFME%NW7+%FQU{})rs-3o^;yy~eI3;Fxuj+8a*I7zK=luR zP7gHDG6XuhRiN5ZCsx5F`QHI-#} z498TH*o$R2rmd&S0oCD{wqXga0>`w?RB_w{e#=VrU8!TrT`jvWj%m-SIi?C5hIL7e zL_o)MOnXVq^L9F>y;2tj@^nnq-fBvxW7<2F;yN7DJ`xc!rH*Nz)Q*4Rn0AV(yHdv# zpXX)1OviLkPF~-HOD!FeCZSbJ9n(QUd7!3aI%--987CZ5a9enx0>^Zarx!_Q5`%0| zQmRBHj_RPM^1CUXs)M4+?}{B)PG&B#uN>G(S&4^BGZcF#btMkVa%d-oRmAK8VLL-* z6~|#YxZIelhio~z6Pv4tXgRzSn`yUtJJB{h*xb6=vmD@w&F^7bjxd(z?&-1|;)Tsr zf$4FK7dDezE-`!-Hct;71+I53?FOD@%V7qAyqf#vI4`P9Ayf-Jd6zZ>4z^OHhyWE5}*iiqOzaWCjr$`h>;M{=*_dipZ*Zi)aniY}z84L$gfYBHBZUz`86k_lG)7SfAV?1`4I?Q`bmM9-^6Ad3?BN%27Qf+gdaNI%IMQ>LEnvFW?0w z(!N8=L5lr~d9U^Flf-R(rHM-f8fxs$Yx@*KQH?=^9(#+S)n?MI@!lUok$uE8fh4;E zao`0_jw^;1q8#3Qx2~4R%itqqIZ8}&=u*3HP!A1tYrtC94Tm3QUQqLq2Y=cd~hxHpUD*QpObitZMt z3_ps*Dusb_si7SBNM{rqwEn*XZb!zPdg#MdWK17Hh94Bf8FSVGWD;sZn2$2op7D^& z=AO{qm>@XEI2`W4kGgCi-S*)}i!mn<$Ru=LfsJ$X;p+Z2<44nlXk*3Zf^3GEOU92D zSwWcLn7Ts#343N{JB#}8qqh)q$@sy17O=|r(S6vu?Sq!{xNMo&GW;;AK5T}V^QeJM zg&j1H9?Dk)KgJC8!N|pocg2|NHaeN}PF!>$opJIK-AQMA*oPm}2mae;2%0%Ev~l)7 zMn7Hf4?lE2wX`~A_^}9FIENoAwsQCkS)9OxU6uTe@hv)?+Te8eX$DIEZlRrvF0I$& z4yanLNB-&RYUFU(tlMiY(KlUSNwyGk-C+sR*{_(1kgbR*ge@dZz)F`{jEpk;fKmfi za5iy@F^9BSU}A2-+RWN9hoEgK3jN_5ybZ3=K8PC(1A7RI8w4419O`DSjkN%Gb1QC~ zci;!LAFyip@tDkTm6@KZJHLg$rPUa72%OC#^mc*4*#}~efWjFzB35uXXJh_B;w%oa zH?at7rwj3b#${Zw*TLg#7%}D$xvr1FUzl9Rh&>i6XBCOPnAKQV&I1OJxy(mzhs||o ziFe?~WcNJrV@sxU#+bwBEH@AP@MA@ef}W7YxrNkY<A{{VVf=$76^CX2jLB|gwM&+P=kPuY=uGZKSIOw A;Q#;t literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/PlayerTraceTable.csv b/titles/sao/data/1/PlayerTraceTable.csv new file mode 100644 index 0000000000000000000000000000000000000000..1d36d9a5cf1d026453524010b8078ba25309cb7b GIT binary patch literal 554 zcmZXRL2kk@5JmTqat97D5*|AV&AKZ#sG_-mDc%vNA`s&C{ogiNrdio-FmE zjEhQQpz_?Ew0Dndi%SAMSoRPLJPEfw%hZHp|{$L))mRbYet}k+E$o zu1<7g@ZqR!gB#De$H-vXrYp>`=)n5K@-Vf~4Hcvu#*Ns2#*e~Y#xb$lsHUP>sS1ix zOxYt5Q>rbnBVhqR61ygW6<`k)w4eaCk1S+zFl8%6VG3RiSvbXyslm dS>vv>qU#06r`2*RbY?5(`HQ$#p?G&%{{ZeebASK< literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/Property.csv b/titles/sao/data/1/Property.csv new file mode 100644 index 0000000000000000000000000000000000000000..d7d8c3082a2e28e5053a0f2f2176870d3dde6b26 GIT binary patch literal 19611 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 b_}$TM`axs;xUiS=)$#&=d+3BGIl=z{s~VC; literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/QuestDropBoostCampaigns.csv b/titles/sao/data/1/QuestDropBoostCampaigns.csv new file mode 100644 index 0000000000000000000000000000000000000000..347b692716eb08db399bad49c418cdb8460ae809 GIT binary patch literal 204 zcmXZSK?;IE7zN zA-u(VA1?^JT_;+THo9JF-I|0~HJ7DY7cAqIV2M_3SBZnnPr_tf#m47`mr5RG&W+TH zZEK+CA9IP30>z0y}?mo literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/QuestEpisodeAppendRewards.csv b/titles/sao/data/1/QuestEpisodeAppendRewards.csv new file mode 100644 index 0000000000000000000000000000000000000000..26efbb1c94d66b2cd4afd18a4d417e477e5ac9f1 GIT binary patch literal 4996 zcmZXXJB}Pj5JY_fyn_xf0JpoVKV{00AxD736A;`nWQbajB6NMli&+`SAH~-Ve&-X8Py(uOA{cqp@ zdjD>D(x2s~7YF$YSD+){26O}*Ku5qG=m>ZK9RW|EBj5#e1e`#}8(f|Q9aeaJ`2`tA zLS&X^v>@ZiN@SL1v>@ZiN@SL1v>@ZiN@SL1v>@Y%$`vC=LX6gSjaFnFS&7lwuF;B& zBP%gl+cjE|abzV%Yr94(GLEdoXl>VM!^jbp8#0cB7;SNl*2n#+mvA>+Z+hvK8!+5m zIe>9B4R>I;8y>*8nuaGZ+zl^aTus9X40oVz&9&U0h6}J6>DCl%M!Gcx!)SYKh+g93 zhU-l)@o~cq*grmiu_3P9fw3Wm2QW6o@C3$&7+%2G5W@+K4FSIODsLD!ri)5*M+{NQ z9Wg|y?}(aJuqmlo1)GwZRj?_kSp}PtnpLnVsaXY^(rdG}y;rwxrDgf{>Q=#CiBh(# zD^bdpHAE>}))1v^SwobvWerivmNi5vTNe1*t!+ouu3PC@m!gzCYlza;ZoCa8g{Ju1 zmETZOREW~nuF{5*qE@1`wX3wDq$rhxDfB}#43reL6s6Iw(m+X3D^VIvX`rO2l_-s- zG*D91N|Z)Z8Yn4B<^E7=|I?0=qL!kxH>DjVMXf|>Z%R8#idu=%-jsHf6txniy(#S| zDN5z>B&etmqoWxe7%6HcMn^L`FjCY?jE-h>V5F#(7#+>%z(`RmF*=&jiIJjIo){@A z#OQ2BCq{}|iP71NPK*?_5~H&jofs)1s7(S?zs zR$_EDqYEQNt;FbRMi)kkT8Yusj3!2kQaLeFREW`RMiV1Nt;A?HqluBCR$?@p(Zonm zD>0hQXkw(uO8xz|5&y%%&|Pk$JY@}6U>F&0z%Vi#z%T;pF*nL%&X6_pF=xn{`Is|g z&3w!O^_&^yIb+D0`J6Fi&3w)nvSvPKfSU2640=P>%t3F+nmOnVSu+PcP(yZkL^lXPqHy=BzVh&75^W4VqD=jUj91v@vAOoHmB6nbQWS zRv)F`4Ouh$-HQn6Su;D%kTtX83|TWfPEfzEZiC21&`%U#z327A z16c3*MdxI}de1NP5mDAu3kkM0)k1=8O|_6+L9IFK_Y~BcvwlxOtvT!W z6x@L^ZL~d{=LKWh7@okGHij24rj6kQ#AfaKd89YQD3A1p80C@P5TiWO19hYyGk=8}VwAta4Kb>dRv*WxPFg{X>ZBFK zC{OfW6QexQ8)B3vdP9uzL~n>up6G!((U19htXDHW?mFiD%mckEF{*Q3AIGT9c|nZo LoEO9>5A^>5?s$RB literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/QuestExistUnit.csv b/titles/sao/data/1/QuestExistUnit.csv new file mode 100644 index 0000000000000000000000000000000000000000..355b290c7c0c52dbda1e3663afba9eeac33504cb GIT binary patch literal 173760 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 Z1?NJ)S{R+&7vj~fliyWwe0{~ui=2?YQE literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/QuestParam.csv b/titles/sao/data/1/QuestParam.csv new file mode 100644 index 0000000000000000000000000000000000000000..705a3a36ac06a8cc79612f401d7b89d412580c97 GIT binary patch literal 91 zcmdPbR|qUkEiMU2EK1DvOwsYl%*`xuNli;E%_)I!LNaqxT~d=F9On$6Iyf&VwYapX bC^fkxGe3`)%TUL}z(B`P$H)MP40yQ!&oCYH literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/QuestRareDrop.csv b/titles/sao/data/1/QuestRareDrop.csv new file mode 100644 index 0000000000000000000000000000000000000000..0d9c8ce604ebcb4097e724dd36efc4e86a33b2f9 GIT binary patch literal 40609 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 delta 1780 zcmXw(c~q2T6vodtjNssa(13ygvb!(@IHM!WiL%Tn6{%26&8V#0LU0KQvttX3h2@Zt zxt)lZ0a6rkj+OgJ<3;h=h`K+BMk0zbG13iNr57Vyyo3&eT`38W1R6%3zm`P)C&Kn0ae>}|c7@bgR^u+A3B%Hm=Pn_Lx z0Rl<_mj49CG#J9gnQ3HaizbquKWT(G%S=euP1cCDKaqA%Cz02a4D)tMw8WzBzC*y2 zN-Xmpm^t@)igWE0>b-RzvAX-wv`;1d;sbPja~cW0X*eSvf;r`3@@7xRS~UYo*-X|` zosRULSv;yegC=?(AzgXQHD4yqB5s4c_AOeieSyYfO~-I!849 zjqws&Q^d68#V9wFFrU5D^^t7k>Kt$K0NOJ00ymLhdKpo28zYPB92(eJ73F^I_rSGdSKvVK(hyCWCNNhd%219eW8Kk{iNsn$L zHtidlU-m6c6n_`&Dw`N@be1(vHs@CB8jqZ%-`4LrqV{uSxS!_~qb|74=OSNM`j5cE zOF+&~!1|wI?)ZfghuV3^whmNoTt?HQGgjI*{t9K*Z!G4;E-2f&Y4q?Fc6aG2llAv- z8v(ssO3Lr3%=m+6$?t<%`6tY#Ycz56FQ5M=GZ>tcnM|_K#%tI4x^CW}uFkQO0aOKA|HmWR71RCyQgwR(N`1=Q?9!?pZ;IOC-eRQ diff --git a/titles/sao/data/1/QuestSpecialRareDropSettings.csv b/titles/sao/data/1/QuestSpecialRareDropSettings.csv new file mode 100644 index 0000000000000000000000000000000000000000..ed7ced571f0f3ba8bee145699ec36f1b1b190e8b GIT binary patch literal 394 zcma)#(F(#a3`O4s|3UT~Y|_o)d{q(ObekVgT^Ix1O05k4-gGD^h)4s?y~)W{YO=ra z)fbjejXCGW@yd0!VDFo@-d0d{J-hO5847QlFP&@+p3K&5^`T$%!S=>gx$z8NkKc|0 zZ@6XGoR;QdO;fi!5nUzz5VH!boDctaM2H08X#|?iK(sW$jA))^inNMpLVAHH6^Wan tR+uTBMgy$_OOPN)_k{JZh6+jQ3lj^I5Tu`x@xPIY;3>iUJu>+f`2afMXF>n~ literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/QuestSpecialRareDrops.csv b/titles/sao/data/1/QuestSpecialRareDrops.csv new file mode 100644 index 0000000000000000000000000000000000000000..623a2643d2ff736d856a34347fea8dc174aedbfa GIT binary patch literal 249 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 literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/Rarity.csv b/titles/sao/data/1/Rarity.csv new file mode 100644 index 0000000000000000000000000000000000000000..40f6f41a0d65db08980001569f071704cf03b395 GIT binary patch literal 177 zcmdPbR|rZh$}Fk$OwkESEiBC}N_Ec9@vU@DEJ7CWFUd$PLKgE)O-al_hz3{Y0aX-d z7Q0py1SOWF@^TsK7#aWplmybeTt+%ZU?B(z7Bbc`0SiG$u#kz41z5;H#|Wg!RL9T^ J$_Fxexd5@?FWLYA literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/ResEarnCampaignShopItems.csv b/titles/sao/data/1/ResEarnCampaignShopItems.csv new file mode 100644 index 0000000000000000000000000000000000000000..e2bc6fdd5cbd79bd8bcee33430797f8cabe368a5 GIT binary patch literal 640 zcma)%&8osM5QX=G@8AOrI5Vl*uEm8L5f%4=2EB08mRJ$r-b`Zsi3MHckdx$_b21)} zHs-X@u9|Dx=)Xg?J=V>#HFjA--%Sbg+Sn2UDh?%J5Q15dr)ug(^>tuxi3J;K`6 zjdAUjx{ZI_ZE3In&xR7u56|7h7lsoi`X{4$7?m*5KbfqD$r2`d9u8LC_L{Q#)|DL*GBAr>KtG4mr}@(4}h^3YK<2PTQbo;c}z?<*a1 al#Zev3LnGiXU7jnI_9VZDrS;Ye1UEo{;}8q literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/ResEarnCampaignShops.csv b/titles/sao/data/1/ResEarnCampaignShops.csv new file mode 100644 index 0000000000000000000000000000000000000000..94138832fd9ced7f99eda22abb5895c2f516112f GIT binary patch literal 826 zcmbu-O-lkn7zgls(035e8_w=(O1Cfxp+xf3anL~rc307{v+gMQLP2#X=n}m_D-|9D zg+7gsZGMbqS9jG;q3|%f`|J!q{?BeG6gVN31TGtKtd}s&8<|3}l*~zgy8ES)MzfgF zq9IVOZ)Z(>cds)5zGvKZPK~z1C=~)tefv2{0}P>h2Z9ZsKmBay zaCmriKgZ$Jy7B1*bKFiJl0Nob*Pr9Zx9i3I`LOx_Jay2HgFAnYU+^;Y7dMPoN`Z(| zNbkq*$M3fg`cy`V_H>;6J4>msP225uUmkfq%pFR^G7G!>fKK(kQqw7B9C5*Y^ulrRya znae=@T_dzY&b1~lA;7cm6S2|c6;Qk%GHv8algm_WVE6Z4R;r9q#uE4G-PJkz+bQ_B RR>>;vEK0!_x$*9r{1@Y37g_)S literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/RewardTable.csv b/titles/sao/data/1/RewardTable.csv new file mode 100644 index 0000000000000000000000000000000000000000..50ea22b8c7aade3bb9f080c226f382f0b3787393 GIT binary patch literal 820935 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%0D=Ov-SOC_Ox!5+D03{^(s_ z2!@a26b&&IuZ3CS)tG23F@x`jq{6hvj(!xcsK~OLir0d)$HpJGc-7gPFu!|gmmLr; z9Pt(!J4yRfAetTVDi)*vG{v;1`GmCjAW7TNDiLDDOnk;GA#FV%qn=;A4C*0BNLvrc zP%QLPZHLAa(Z;WxB*Nrpyh7l*Q&d?ed>u+e@1aW_&Zr3^I?PrF)g^k}DZ+gNh3~mg ztJj^P4%kE}RI9h0qR!Q{Cn`GdTy5_(ps>jI*fboM6^rpB;kabN2#b+n+H)Be2=jh{LQ<6JJAIFA4k~>G|mt#tD=jij`h?4L| za+H|?zdc4tlIBR;mBW*3yMjkWOPfgjhwYLyM{4u1U6STVl7}Ztk|X~WnGTa>ZZvE@ zR`uJ??4AN2mu*9DwaRuUs$27ePGC@RtcDU zzCXyJnPsJviYQ-PABEjO(dF@z?B@384_>HZ50STPpgKD}0mznmNM~`193#v9Qj9cv zs0~o6WM3G7urA_D^%Wem&Ls$D%J|@4-T*hyen6`94#fub4QcBK1d3%RgZoCb@dHsCpm+>JOLAgC!oQrt zfI8&wn=ziAIAYwsUWcFnV8kwf5r0k!29Uc2FhcwQARVRl0>nS&!$BVmQ4cEWd^29d zh8eH!LGh}h&H``_eu!6{wh1srXtcukdli&rew3CMWm?Hx&ZN>#3~o46VcKJX(WXi9s)IUVd_&q}0kLXyS(SIb zxo59l+GPi%Hb9th7W*NPAKL;aAN!0nMKvCpPe_{&$hHCc69>%DF6tI(Jt1v9AlnA$ z4|W2Cr1gZf^{Zkr#F$Qq$Ye!C8^3mv2$P@j8V(p8EV~+xEcPgDT%zX&NTMUO(HvSm zH$V~{L0zKf21ud~=UhI7u3_3d9#2VR5vnT5V*?~fX5R2HYI!iai2Yat^&canOEvA1cCXwkU=Vw)^{kC?~RKI_Q8JUvK~%W(MECV5-h>y%9QesH8VcaE@f*esId&e8dRSdx1j zoll4*xpRaUcrU&09DN?KsW#0~W`k^2|g_8}=8hkN^@11bx^5rw>><3Dv+4FK}60G!eQ&=T1I(jfqRd3}7Q zosj`#WdWE%^q|VI0ZeiWV5)Qg$dLeO6BC`>>9iq2H^WJQESHZ_@*yUugtKQjX~4|4 z0EP`GfWfr@h7BixF-Aa|Fju_WaX6^NQ2XJEHxZs;rqOwn3;;776DyC?PJoFMz)Tk) zm`1ydDEwp|1TfPD2$dFqolgKWJphhGK<#`2nCSxK>9e6y4{IKWdyCXdrfU(NDmjAj zFR7Hr+aPbEOFnI$@){(`o9B{Gg@y%Is2+O0blwGTCC5TKh9^Gwmp8szdm{m5vck*s zvH_hL(~DdfXiOLz4vSyd!yD79-1y^i91f5{cSC+T4hM({M|ajNsIfI#e%BiRrD6~5 zPc=dlAu!(C8vmtJGN3*>A*9U*M4d#&59>kLw4RW*9*}K~|5Cvx!l`?~I)kRHie)E* z`bMxzJTlMM1 z$_*aar>EEB*c`R;LZ9nkY4*j+%?cQyaRSKQ(!+?1{W#8xl^gev3rR1I;rN~e>Y2xj zl^X-HNW5NjPL$hm99*m|K;?s6b7eeW%TjA+Wq!|mtkpoL$_Issd~jkyn{YiA zYmy5%E06s|L>w6916FI&^oE2x8UjUKD9>IPs=EB4Gz)GFku}T*tlFTHv|sa>5eOo! zc9agI3=OMf{-{P*Cj+9HkH>LjJt1xVfUp|<;%ip2o{+YFK$r|8L6u&a42fvt2coLc z__|#oG9N_t4DqdEl>G5H4qmaUe2|Vi^8wgODp>l(mlRgp3t;Ah13>N;z|02+fYB;I zCjSxMF8x|168Dd<MG>q%UHrFztZ&qc93TTntS}O~(ak2OJPh$E*dRm-ImP(jGV< z3_qU70V3^>4&yV!PXVj&147?4)RLUS!9=VxMn(g#bZ)iRBK?m9navT zLu8w`_Zx5nh(=y*dNx(>Y?I}q(dJQTlB79Oo34uNZJHxV9^2{|HTHR@Sa}h&ES%IA=Oq08=c_bb0oNc53@P0!|2Gfisj?{U+1z8H_ zl-y(K;r#|m?krv3Z@@vrP0{oHhH@yJ z7F;ERyv=w?ZX`wd@nzTrgM<}aJj(oZ&X5ci*UV_op?uDxrRKu(31>i^>2TKef4+~%{ zpuF7?fb|$G?J&epDJ;Q`f#VF7bmf5o3>{ra5H>)}0QbQC5vmiiA*Plxx}S{=qYYpn zsfS$CIgtRMatR;jzhTsp09|>IDw)?YO2DSSsDu+HW140SCv5N*!OzU2EN0;3EGMNidg!HKjhNUpeOO$_z{VYI54L;J(T z_!zM+Y|?zya%eswZ9X8|iVH&~z0!I@+Im2?6&EVEf~56?wDqfEL5pfRyzhaCHh%3S z5e(OGFSJ|^xU6}?e^Qb;wI%lzmnBWK+T2%MmK^+6@B50&lBP9d6M8yRXfj1)gq}z@ zVRy!GwdoYGOFDSfrgOtCImoLv9f7-~Ib)m96rNNYNUpf3r{h$TN1B#J=17tb=e>7x zB*`O9t4(tx$soPRfT_{}U>pg6HZhlq`X?Kh@&w2N=NKhF*yJPf zHHQITY$3^XEeaFi8D<(?kC)$6{rJZlW8U8QE)qPZ zdI+Y`E^`EaG7sv(Ot(r4z|JQCQ>A)HD@pFvIA1_`VLOxm$E+vqS+A^ zVlh-sQ%rlBPe_{&l9Ut}ukSpalgnbT2Dw@56Dm~^P zwUb0}PL4j%G7!lA+Lu)@ahNbeM0=@CN$&exK3RC@QJf*U?{h6V_^megeXb=(D5vDP z=0_NXMXK|Jo0X*FQTsAUnng)^eY8tDY9;Bkvr96^x_1n@$8$becvlUlB|WFAO^v)c zV(+(`lj8mzawN%Pea<#jsp;OmnB00-fu<#o@l=~TM@V{`?HKtaNd=j1^l3m#bZ6=M zWZ_+hX}VP}7G;zb*m**n?z(2p{GBgLW#Fbi-JUGGzwJETo-Dj$tXG~MZceJ+b-KnT zp~CvCr;gRM{w@F?JaOilY+d>c)A|n zZ*rTl>byv9Bwe2@#6Z)eOpTGq_crmnNI5zh!z7AX;^@6LyYD&>(~@3z+e3>a9fN7f zBb3#qNlHD@`w_~LWEzVX^F2>*oQsT0xCvnSwLwKh|ZG! zsP^N>_y(n1rPTR{P{~^aZ(g*5z`C87TtV;vM6V$5M`J=T+zj4L3_28j@inh!KM0G0 znDJafu$y52QMK%anNb&?lr_DE7@9Hobohn#hmP@aC+j+;^fK-Lynuq+P3zf9TfeSt zvKJM8f_exN($=qvh4H+P22^6KjcDW7P7-1AbISc~!=ocCdhI>+nc=6WXxc330LucH z;pd%tonw4g4QBz&@H+sER{>z(T6{YIBu;>grWvo8*_S<-YB+jLK8#-CRYup<0A&bq^%#l zFoUX^6WlkVjUR|AfxPR~OsIvHBY%eY!&%999fgUP{bDjp*Bu$buB(i|OMj7Z2Vlzx zwaYMq&UWOmn~9&hep24iO2Mm7M){jt^CG;srX{4HQQSKPhDSwEsiL zW){FxQZRraD?qN^W{i&>;x@w%zFmcptyw>)fO}XaQxVoBfKzK!4FkHI{)Z|$VSGc{%h8QhD>sjud+^); z()@@sSm~;R7>NqnBVARDf2f)heV}DD34dB*+y56>FMmE{YEVvK2)@6NvD-vlJjj#I-Ts2oM>Cp5hzJy$8d6t9^H?6b~e~0&6Omb z26jmYs3aZEyQDdi9;6~lHFZ9Gc1;{hKk+O*`sk=ooj zx*ZSr&|#Wx^WcacqMD5*!I*u%rVvT)9G!&_IB43wb9B2x?1Puw;Q*|sHa%5S^ zfo}%^f4^D$gH@arFhZkW`(b8e31(7eS6)=MD06;Q$G9Y$DvjXR*sywRu!McqA8Qu$E4R020 zAYM!NpcAV;&P_mXuQGn{g(U!L>7E6EH6JJ*a`hN)#1lKfR6Zz72LvoMChSX0MJh0r z4+_%}F)+#pEcpOZ`Jf<6K7gXNLts8&(Fc&q2L^M*!*1K2fAFds znNLWYkGK;gTr_%tTa)&*o{+YFK(us6_k^VNgtYYoLgRUWN5&J;#t#IS?)(z(9AUJK z{`RFipE-Wu$-1>E$$jZ=NfVuuDNy^;-IBH78NKgIcT0{y&fafNJU>{{L7SQ3UfA5p z`L)zU5F{lIT}d9>0ZOuhGup(D+ThgGDoJz3HZkOzB&Q+PYr4(byHp=6(+o+6vn*1Z z=19FC3m!?DBS{_$9!Z)bNgm@V$(x$|^;;`zx!%xLo%eYLss^zdCV zv}ot)_Qdm(r)RqN+Y`@E7FmWQemo*12>rzHF_Dm^+Y`@E7FFD6khe8juJ_4W%8)!r zl4Pw)hvY$0y-W4U$_XShjpbTn>m9#KiyTbaJsiE0Bvwk)CerrNyChAL+B`y8lAgaL zd4w`0(HGKD4I+|guC6zWVu;fwR6r=3?}>+O%y07*g?zm%?8oa$lubrMHYT_lfcyCG%2@Tv2U}#ycz3DxR%Yt7%ur z-t9usx@6O?kiFW4y>N-9T_Jk43tNH`O}j$$Viy>njXrr`{5=K>#Sn^O*@E~=9uF_n z(Bc%!l*Sja<8zlbXW2Hw6`#Y$+_q^O(s1}^lCer$G>NwR@v;)E+ctUKHr6b+woTuV zzUyrUUpa!$VaNxIC(0^Yo2Pr$oXyGz_JWaC{=OOi>F)LxV!A?Iv z_!so5jfl6_aM#QMDx?IWG10<_Q7%<2B|NGzF~1!cm{Q$vO%kKkkZ?#tkV@uu{1a7L zGO};S4Ush(Fs}D$KliYap$dV35h*esZp}{8d_vm%0SPCKtO)`M*|+0IFWioQs<1-q z*-KkLdf|AfvchYXhzMVP>Lk_T#vn*R%dtG_wLuPSXj2w&y`Re%_({61b0Qp5p2)Le zpQ?;3fSC~v0J&QLGb0=TQYS#D-xB~-Qf*u+sVJh0g2D!fVSr-oIBekYwNI5q1cO$3t9o+s8LX)^65iLC_CI)G6VleNiiIhvu+VrSmhc;$Bm&wW4A@#TT5?_=dEX z4+u8()r(25DYi6gHy@CiV!u?ZiDuD>BG&e!g>8!c0%2$;NJ;YvY4Zn!Q|yx5cp`Ijm7OVyg((u4D`talRd zCcAF9Trkd;l<;_@MmAC;J|0<&p=W{^&%}_wFgc7aOb_kEL}Q8JOdFatrpI<*UP)pt{w!(EGE(pN(tYt&r`GfKEai&_XNIIF3Kb~m$~-tTB#&LZ zBu6l&MRB{#Y7r&z`*oDCuDwVie=Os9eU|dYSDl8UURB5>O?*5$Geo_dI7D}vuJ_V? z@olGW6?t;AstV9weA#J8dLiKcv)=K=mz{>>!IRo_`lOfKo~3;8PT-@YV#neo~3-L^uvUgOS-Lh{4SEHYCe?2lyme>l2q(z$)k5kI+!xuqRk_eC5btx z!cLM$C`*zfpCvx54FWb7&VCDmtNAb0XcmBp9g7JAxO!Xwj@Ksu=@0-j)VlrFa>YF` zbOgxx-7$e4R4y}snJ!+#L()#8Ax0+g6MVLo!1NH(mdhqU>kR`qQ|U&4&<}IWXy})+NNm zJa=NE@x%_cfg*2fx+(zNWLJY0Xp&J&)N1JDnV8(uSiL(=CF#7ex9I?tq{DfaG)Iy=CIaQ9oKf`DPnCk>U5?UXC=n)Hxqu(lg#5>#? zrMcYkD510T7u7xZ{(2sq!Jh#3(q+Hs_nVjY#&O-8iK8u8J~3GSF2&Jr)j(o!WA-jg zPxu(@ZMpNUVhG=0C!{BQK&%?`4*Zgw$67?a^o&0swPgKPb>w`1zwE~vK}$;Xkwd03 z3C)iV@oDn`*-BX7e90j#*vCB-R6xW6;WF9#*vCBEqUWeH55xi>u1|l zLutu_Bc>hIP+Icfh>HWAqG`#4BT4QYU6(tmp>&%EM{E;}tQ=jJJG?liQglhFuCL17Sya~%e9x(i@A{Y$Kb`sovbr*ngYT?EYu5A z2x;>H*_R5cjL>>Q+WK`CuU-cA-Ah})Di)@wGD73oNgKa*k_b+1F~VpW0=K0C?|*^K zlDsVybi$=2Z%YN<^)lMLEfw@WPz+z`c}znnGNwhQQ<9EHEdVG9A0msAbRh1M+(LBk z$25dJeDto5(3Cd+Gm2l@UhqeX{<+SK$BXiIKAaoZtY^)yV>2fRmgBFjYDL z%yj~wP0VHrkOv!dT10^LSU-kY8*Sdo|D$rr0vI-&04Bf%Fl;yhE7NJhEctj@W|bIf zaT|qQ!}((xT{qhQs8%9CjPY`W)x-&4rmKfw8tpRg;3xASfSGQU7J!|P00an`9soz; zgAJ;Y$c7+p_RzzM^o+^vM->v?Ck5g4p_^2yJ;aa7Ba8fuABBVD&2v?`a*UiI(3=KT z@}{}u(|}>I4UoK9hL0-&jFldS&#P8soU)nIALtvHUg}Lu41bQ3xvrINj1!ax*KjHP zC**j_kSpCE6-|PRRxR$KGGwY2y&xu|A$(UShQZbZ_l9T@T1BxRRZ)1oRjLu12!Zid ztjc^HOQHFMwE2Ljlb9h=FDB%OR_H}YTMx)q6#G$uh1L@iS-MPF70XTr_1#Guzjl%c zlbH;jOH`; z1~4lNfW_G|cli|;12};SkXhL)w~>_%AZ@&^CXJripjE;!#ek~fA^?VbO@Yd2Jof*i z!V7P>sUBvyYLp^gvIkP8J8ps>eC!tR69+~3Okx!nDT@ zjN(--M@hD7E!#C{-^^)aM;HPm0A5tDtkZOb;#oXm5H6Pg@9mdD@gY$hR;7z|WpO7~H!Lv|iq$&UGS*-Y= zm$aU}wDqfEIm8vbGMh z^3WsCgZ{9JGbEkn@@=Fc>2=^HiTttJjxRZW?w#)*jW)fl+1oTn9QoS`-yh!U zGTQVyL2YU_Q+aLr8`|u`2O%sq;3^?a&NVGVkKut9y}q* zL@e=)%aA;HO36sc8&8)LzL0b*arE(E{z*?r?mS&i_(F1zrOOFlNba$8xh(~fJ4?53 zo`&eo(&dCNBy0WR0S%rv<1<7c>BLvV6a|foq!XSb50dhPFTTFYBxS-wCr9u2-T(d% zk|s%Im!o$Q&qFS-NRmhIk~B$@JVIHLCP|VIGcfPQ2A4)bi`p_n!K&iK zDj;pcqkzJ|zl@2g?!-jyNenhsg~_>&{lrXf40OnTYNp2!VqI5|oa+e5&DoP`(Myb6 zL23<|)%4D0T58T5Rmb1EF7iuLarW&GzA46Lel)O5T7=GK*B*%FQlGfJt3{v zfW$5UH&khh?wc0Oi4r%3Biytk!?KwBIDsM)GLV$%)gV5BQ4M@ zF#k+`cy>YlnS9i12$|$JFT)y+F$y+Br2P=c&t=4t7SwDA`DgNDO*E`$FTawH#U&wW zJt4o5kC%gpNaKn4jeMS1!GpJGmyz$81d*faQhi1$a6KK;#fKX`x*!cu7)1^`ZJO|@nc5ea9bOHWS?uF52y2`8B zL(D+Aw8y`sQkGXCZ=#3%j2~$vdGlQIIYzU64XWf#bIJc+?u9;PdC&e;bHI3OTMU<~ z?9U<%h~BgRiHU?Cvkc2L8KgbSME)}d%-7V9QA@~RyCE_48`7!~V!lWA&l08~s_bwv z2YTpId-g8`##@_w&EqW7$oznZA6hR!)Cs;KtV}WMIrkCL)&t_o4vFMRoQx(25)xVZ zPz=1}cn#_s5q@lhJK=Dd{9GZTkLOK3zKnFlHgtgG^ClmXBdAOAd6N&x!EZ@EZ}K5& zTDjS|Z1N##IwkqM$%mu^SCY@0d`J%RQqR}(CLfXxR<;RVG5p6SACl%ulFmxn=W++b z3wSedviHlEK;rvf&5_zXb_Q%STo*1o+Vo_$_0AWMhNKq6|L?x-8T6U-FZT2@48k@sTgBbFJA(QHXTbe>O2O)-4>#89sTPj zU&Wa2{TO|<=~zn2~dn3lY@xwCZNZPXH#h1u)j6XAg`S0rDB5T^v2= z8KP9e$Y$v1Qmn8csPbw{HgKtew;)zPY+#oo2)-Mz);4$>Q-E4OWe+A4WNUqfZj*k# zbc95SuCVIOVNI24)`h;kN>L{y4SfwvuOKES8Vol>BQ!mi5bP&W>cJ9BG3{wSA#MIxK)oY7g((P> zX+0rr{aip*R=|7zf~56?wDnUlb~3m}(?qoKqZ9m?YB5g3fzk5)c{wZ~e<`Tl(F{Mm zVO7tX0U&z|V1}Po{sxe{1u(iem z+o>4RC0=E8y|RukaVKdDV1%DlGJu>35I!~oykGjFTkyP%Rm!b7LP#m3H_ieuSas)C z+Tg?uYit904yV9=A!N`)Wd!<>BLe?EG6Ju4iL=QWcZKPYn3!

    }T*Dv09jpiGjhT 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

    MA*FNfIr5dfD*CK{2DA_Bi{_M% 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ÌyEyAA8$Ky?+bFg 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 z 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

      !-LFKH{g%PjT^U;>w^YE_O{^-ENADTVDM@vRnlc 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 BEYJV| literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/SideQuest.csv b/titles/sao/data/1/SideQuest.csv new file mode 100644 index 0000000000000000000000000000000000000000..970ce37a630a8dc4a898660619f230f545348d24 GIT binary patch literal 2554 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 L;sgZo>XrWoe;uaX literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/SkillCutin.csv b/titles/sao/data/1/SkillCutin.csv new file mode 100644 index 0000000000000000000000000000000000000000..ccae1b000fd0c46ac252be0f82a41c9d4232823e GIT binary patch literal 63707 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 literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/SkillTable.csv b/titles/sao/data/1/SkillTable.csv new file mode 100644 index 0000000000000000000000000000000000000000..4273c80205b1e182b0f58999e99cc60ec6cdd761 GIT binary patch literal 226670 zcmZs^%dRv%mSi_CAm2d`kU*ak_cPS60nJ)3(2Xt#jV@>=>Gfr{Y+vY+UHNC$|Hb-9 zCu!W9=5&wXfBH}V+kg3=|KmUY{rBe||MosU)+PDtzwdwi+voh;)u>sjGD7C{C$nY@1@KCeDJyJzx4T^Pk(;X=jvs3+5e`Vy%^`) z^msp3zi!jt`PgFJrn~z1Q0q3m)#ry?x9O~_so}8zx9P2cYf^Kh<86BD&r2~2e4FkH z=pPsO)ug&jcl~)O_Evu-()Jg{RKTB~9d5-e@%`-bso4GO^r6`O?DV17{nF`G?0)I< zDt5p0c@?`)m)#fUA~A5EF4yO`ACHjwQXSrpCD~*8tB()K9@AZY?txY<66P_TbuFcs z=jUU3YmkbV(2wb^!6DgW`YV8Bx1S%kX!`5VOEOQ2$Jt>q+N4-|oE>h-9Ar{yyIhj} zI4qxMrw_@VXQvOzo|j&)WY0^dSF-1&&nww;x@`|PbXOl| zpnn}3uj#EmFU6DyUej0Ca>UqcI&0vHB(X;AHJ$b6C730CO>YHCFo(=*dh5?iFc0(V z>~0ID9=y)}s*Kb0@2>{pb@sRgbF{wBJ|BX;&pt;mEiqaLE}bsHRL}d;=LmN5-@o*E z1$$4IJ(tu_%_j}@dwRSdfAsP6cRyBl-_u=v+#%+RPH*+O$2qIxd;02HdUPbrhs%34 zHE>00jdb};XZ`sh*k^hxfMB|UlHzB2>(4vF)S}PXT?A7B4cq7JZwdDEqaJ_G9;-4p z$;yAuF1KF3z}e;2OTvFHeU4t%<&QpgmrE}-=lqTBm`Yspg$~5(V*LjJcp{CVS!ScJ_%jAf=U;VYc3?h0ey386dm1z~7 z5`Wi&iIj>zZ%ympwP7MrF_&wPPY3hn^|x#{^nIwACNB>ylmd+X6M{mT6s?D>?;S zUuF^ky^#H`F*B$rM2@v)BBdaAWb4fg&LCWOCQ=I8p8c*r6Sye27EPoS^n&@jCQZN! zD$d+@RtOS2pRa&x(*P^r+B84}^d;7(0T9q@;P3b}T?YZ>>nPAcethoiH#Y6~oO*h$ z`|}8h8~7cYX5c}OasM5cMn%U$`0E_Ox(0DwYLjQ-7&Sly%u+N~%>)5;6{|5`4R9`n z^{CCG<#*eKSZ5dhNfBdE){{4Bp1Q zK`I8h_9S*+yA*Ul-DV%nMbt$94-;kV8=#V?1L`*Z%|!oDgr2l-H)sW4O5!*DdHdcm zXwk>RxyZ)gZ7f_qUjfI%1|K9a*NZaZS)sWl#klH>8Dh3ovp`5YpH2 zz+mN?bS(Pj{f3!q(y{0V5I5{xlTy-;fNt2i1)EOsa|<>Y*@s)O!HAt(u)&C%1CYuc z62uB-?)v$ik;Kbgfp6JvSh@bZqtT+^XNB2 z8RgZDGaB)7U8F6Imr70LP<$w??zp)ohlqFF+<;mXZx#_d*Q7-3c_Mk&!LA2ItT_#M5>I~BhOSFobi~pPab+swd$&8D zZpf)4rmjg+s{Z6o;_8N+LSpNhAf(Ⓢ2GCCZ*)h(|Td;noKx)l-ZkW5+$|WEZ{+( z5$JIAVB+q!#43roTQG~`9dox}7RNi@4q#iM5lc$FW9_b=lW#C~U!S8RJ97$y8#g~p|#FVSE*_kdImZf!eZU+L&Cb=#@ zeCUgG5OQ_j$t)lUSxwlTTd-^5`Z}($YvS5Ew!tR|*VN7D1mRk``5a-_(CPCGdd7DB zoInk_`><}#6_tJ@*|l>fhk$G63~DYgN!HJaw1Adl{hUE1Qk=*dI+2b+Yrc+7pkojP z*V2iUf_7E+^>hNKg6ru3SrDBY*quWtz$&<&4zLQYrvt2lYw6er_h6#bI=cN_jn(tJ zo;>rpQ~qP^oIXDke5{)j@Tlv~R}1cAy__pL6nw0iGjUxiqOpCfnKL*Pe5{)j$y&%A z_+#Ci!J*(|?VL!KLT;`eY|k;c0;hrxwXy<71O?SFCAh~rIsq%FLFDssvV1-kdM( z&rbz4o@tXojk(Gp!;ZUsjE6HgBpe4vMZFAq2sLWeZO{6e z4gk+?rinoZp)BNMJe(_Q1u4?A9Z*5&9oEN~IFlodKE}lvl!82-e~gbaInwB3e4N1{ z;Mll)zCk!XE+7KBy{h%-;t_;n<5FS;92b|3@DXrK+EoImftp-sJ- z8`P-tc`xQv)Ims9?PMS|8$9S^-~g#B(J>Crq!c7L$H4(gL3@VBSU8i8LC5oWID=Nu zcax8CaVDi8@jNch;81W(Tt3H&+M_EBXcEYR9xVQi-`15pCHa3~o z4tUb2t=eNeTz1IaN3V~Mv2e%dzVbLYe}0DG7&wCrI*Z!)H!35KxSpdBZI%sVNrr#>%B1k?K&P7^3kJNZLgAPDvr7>{^hkWDV^7#hf zn7Duq!0~VaV`CoU;Q}@Q$HHYB+`Y&uI2LXJ`gs*izn$r7*>v~Dz_~yVx)mS)W>AA} z#mByJg^XYpyYX+3nhe%y{2QPnP`6``F>ofOAh9t94$unH;E8iLxF?Y78qo#xk15(d zy~0iBTS8yvIW~@#X<#%P)XC?#xO|=v)Tlhi#s$nO_#77(FstBmTwK6t_{7AiiFP1J zqGggxIK^E*_v-k>!JVJ;?*{I8gN{L$aJ>UO>@k*4{96~$U-C$C|M6r?Zb<6VZlmk+ zqBf-FgCqKhhiig{x{Snq;^79Ah7WfU6W0U{RYc-GF>wt_L+xFfSoS8Rp|(3s&hA$n z`i}7FtU5*dj?m+Rlk2)-M}J=K3Px_hu#1S)8w)peq!UMOzFr7EJIH93{U&n-ww_3&fp7O*1vdDT-_ zJO*Gr&66#;EwM^s=Kvl&Vj?r2Ex8k@xgJT(-1T!t3oqB7N5=#gR<1#XqdS3<>(5KZ z2#l8-QZjlB@p1!NMNbwDXmwoOC>4oD%v=|l`wGu`++33u(duI78cZ}g=J0b(N<#t| zL)YMxkS)0dDB^qD76ye=O|(MGlAU??A+Jq9!1RD*XI@lFV~-+ z3S#9N917y(`twoHZNQi?oEE*Lb=Hr65@mPq$zd#MCW_f*yA~-GWsR zPq$zd#L@xS{dPL?7UB~}cm14!XGiYqbN3rN*PoYy9z%V%uQQx3utBJA4g1*EH zH`i6wYH0iS!p#jh6ntUln)G6*3HQRz4LB5ht)El7I*n=!Uu?>CkyF9fS~?X$LHQu% z*VkG)1`%IsPxDg0D!l_HjlR~?0Z5~M-v3%l2N(rk>*xTZ;A;&X+utbYeypFH&uvU! z>*nfn_mxYJlNG4INiW9H2$X~#+_iG9=`f}*Nl^VZaa}Q)uk~^UEuqJ7&74RDAuIZ8 z-JD5jXgl{>J124^(%0HKflj0k&Fk0tIgyG&jOl9)ok%BAcavQ?CIrAD@<{2mh7ORm z(0x%;j_%j9CePrtbCj@%Yze&9&9MXS@=m0$^>X{U+q6z@K4&JbkJIPTFoU=@PM|bY z+`iVtxuR3Ub#W$#hU?-CN<$~Vmqxn!EixLqAi5Xp;{-hL`6BddZJaBLh(AQg`ZyEUr6QJUeVjpwc;g}y?IMVnZ%@D2 znIjGiDh>%MXQbS}h@fGixNE^|c+vT%v)}oUbrO&Yz!Q zI6lsx!qB}Q7e_^BGOh}E7GeJQ$%ON#B4$x6ZN6wyC6cTIW z1g1u6!KC{LMmTc8xQ9OL4+LM!NbI5rLv1(_vd;{az2 zj*l}r6&xQ2s2D_!F>)rYAPwtf-QW^~d9MEVEf)rn*jbRJzBLz zV+;EpFPEB7&@uQPFBdQ|_#P{lJ#b&qG58)Qx1W0u-{a$s&$;q+TjLi2xvzhZi*tbz zkUmV1>vQ$nqsUvRZx2FxXR;yq9v8>8OF$=v_t-cS{o@)Of^QYyx;lx{!SQh}(gJFV zzQ@QJlz`k)yvNF!w15;jJAVi`+j1_@du&R_@3C?Lvkbod_qOl5GxHuNml6|#?=f=O z1$TKz+Iwu=@ww*bdraJZ?!AfkcsPH43OE+dpak>`83RW}6@ko=ad41|K&vqh4p0Kx zNWE2c^;`d@LGT{~W~v)>1X}a)aIQ@M_s=WZ-c?)o{wh>`2hBV#nd$2G{n(+6x^ zf8Hwk8%1x7+>olva|k0hphWcU*PCs*CWz=MY%sd((+Nf^#kO1*K|>9@#pM}mgO<== z;eTW1np7;3xo~q0T0wUOH@6^;#uvfPEwF-Y%Dr)O3&zpBadQhc7BO=G!g^y7GxrS~ za%{?d14kBKuE80LSh)rriN$u8XwA(FlClq+5WJ z*8Zc>*vscW@yFVKuyFfC0BTd+CpsT}aChH8f_*S|-@rY@+%<@jB-Rh!t^rDV zId+NZxi4Ne$1CRtch^PQr}`%HgS{J4O4>DiBwlr1wM9u?#x29(b(u4_esFkAqNLj9 zF~;LHX(jy_@q^225+zke%isr}*W}2qAAH_|$*v!a-h#=lAAH_|$*v!K-h#=lALrCe zJ$6;~r#OlaJ9U&efQJQ_cLJ>|HijQeUV~QBLyX63&@pMf*sNIft zHfr|^;)N={QTXT2U^vg?Qax^8ms-FWu1U$0 zl#Ik|*ma3k(%EP|9wq|oujy^#S7y6NVlw{ovo6nNDkkk&KWpwAuR@ zeVtL#6Lzhh%CwR<6OZ|nK}p#ZZ*qLr?zwI2SwCy{0K_B`uipa@limUNtla}-o#g71 z^#LeJ5?!~)25Xdbgss~vU^7cq)A^%>M`g{Pz^SA!nXU_*O0L&)Rh3;?^VaN{oH4m> z&!m*J+5D{C6S*k4e$S-WNe|c>K9Nz<@wJXmq?IJUujLb&nDo%B=M#yNIMI*)I3v*O zq-X4UK1w7e-I=v~0F*@gb$o!0$u)dzw7Ss@%i6v9oCLCFZ$2lruGQ1$kuhuGIz0go zyduE8UZ3ZRj$82$edYuNM+XPnM6f?MdPzpPXra~CjGNs&m<~pJ#cLk_admM zGK|)`JrPvYs8D3wo&X{WSod{%0@hINcRZ}w1mTeSJr=(>@`jQ#^{|uWB)fk?*v*n{@B6u&>K|zc=rB{%QL88^kVnNqu4Hi zjJ~=O?%%jPF4C20Q2xf|K`Iz&u`0-nH<@760RN5AgLE*uY2)-vCKx@Gnmb!t1tY7( z-*`QjnP5}|{f*f(v5@MHrT-hV7cffxjoAyBVEh}e7l4vBt$$JEKD^^g0Y$eC%oxn^B|?9@6`Xs z=b2oT9HR$`lGvj^iMQRafXqxDtLHMER&9^|#_O5%0%;TUH)hY|RC3H-z$}n|WA*}A zAQ5c5UO*N|uHK7%z$!UTPeNUk9HVyvPjL0BI=>}|u(5eAaVj}3&!CiaV*4AD$2I!) z!+MR&gS3*K_ha)QrKDGazwvn{`bX{5dbR%>r)M%T>3K0*dM2%;7w)5{S|phjkF0TPf>KYy`OkM+B--DSb!sQJ?NfE~iHg8Cj zOs2%=HHnh(*?$3g3)+#Z00;OR%I!s}f>XYg@)=jWP@cod9Y zgVxchjYqW&Jo37Ng~#Xh=Pe`t0H-&^b;%Xc3p>35rJ`e4tN-*|{Xj;Q(Pc!O^14vp zd+=kqs`NnUsJ2N)6|&#yLZhQ*l90V_BZO2TWeXAC<28wpdW;5f=r2i3YA&mcm%SyC zGQ_ueOGZnTxeP;oci5|lE;ge;Qt+RcV}2alwH0wDW#lk@TxUp^nL!YVqwXof-Pj8CB%2crtm2iNjs&fp2@AO8<| zQ{S~YN1~;DAQKj_hhnlVEyM&ix@e0FT#_hFnafyc1U2FhrWZDF%N*Kb1D7~Z`TB?2 zJp-Xe_ckmu5*tXFB@SXgQSD(UqS_ouA~Omi6|R02Uu1$pq_s_%nGde560g5Ts6sO# zj4s+@1*dhbtriQFni-)|S8dt(>r!*CiMANRMptdIgpE{ZWM`0Qa--3f9%Bm|MO%J% zLr&=&i{lj#k;_;_TkimgT!td=5F`^}4EH)DVXvAhGXtTPSzGDEjKmjGWB~(?E&Kw> z0ocMOQI-ttc<9-ne~SXk*+O?FVBQkAz`geEA2^baFP+Q%VI^H^1tFS4RXpN1A;j1XJc$D~t)4+pZP9^mHmW}}1|rd+(lj1% zYRl+UGR7qi>VReB@QH&;*hkRBgDy1z%Y?)$cBx6mG=1Hoo8D2$7_DCvDrqb6uVWM{ zX_dsS;Ce=(4p_!-O(P`9IE|Ra0&-}(t`R~qPMTiV2sx7Rx<0UR_c+%S+%vDLNfv;6|q&{%mpD-RiSkC9D^1$!=t~qjp%^IZ{&Cj!o@Au ze^ebae~ly{x!4Xa2V88C96A3D1 z6M5qzjV4~1dE+CET3YY=D)M?h7)`vQ;8;l`ORN9yM^xJWKF6Y;r_tf@k|~3>nOP?3g)x$68RpO!Nt4z#WClUxqV4#|q(fcE z%QjinrhtV4)Y>w?#z}%IXrt0tNu$x0Q5`P{YHf3^PLM{UEh>$hG`ea#cG76HMJM(g zcl-`*$4|OYYs*v_KN*s^Wz4;X?ogb$c-&;l9C6FGBQ0=_Ed?2e@sim=&XC(~{En4u z;u*;(jFU8JZPT{N4ij8N1VD|wJ)j&YJEtu3>7jHJmW zZpTMbTeMZ=x_BgD zk%L^F3ONVlJdGjIRSe|x&?jvITC5Rd#354gxH$n?cSo0f~id(lVeo z4zkG^s`$qSt!l2LfvTC{mUVS>2M0N&qN>b;RmDO!Le-8{b|Z(hswrga)Cg4-c1%Y+ zWRq5vyP6!q(Wo+Tr8!(=qYhSLpWVnMtgakweZxgANd{){Fp*0vYjz`Z633FPu8a^C z65@#zS!n(deQp_Hjt*${Cb5{9~immGQwpHfdeC+sUaMjapZXdCui%)Zv=?W{F_DyH=*~CJ$ zOU=m~OX=P^lN}iI#4M44gWNKQwiw7Ihqm~~C5N`yM+ke#YfUhZ zC$O}ZNr|9Cvx?{whEDF_AQiNzB-eS64qA6qrt=^zEe}|9!4tW@N~`*E=1EFfB0Z;e zbh}a34R;#KxgCR|Yz&|L?NMO`Ev`ZK_8=u}3`LIb==u=Wm$I-qz@yPAY)t`NKZspi@>hB8Ob22^*1(Divc=?`s~_(Y}Kvf9EKVE`+M?tJl~Z z=FzC*l~B%c9*s)YY^~haLNchl6T?b+SM9Z4K`g7H+*eu;3CSAkOmk-^vM*HMufP!J zNRK7R%C6#l4J7-{ghVQvkXXR1U-wvGCh59Ik(QOwVIPv~i>mG$#R{FE<(d2YaIoG3GcL~<@k1905N|)9_3ZZIG)pd{}9ji#U z4iXYk8Pv6qkWIpOr|TVtW6SBj29h#|sOuluME0#CH#0fmgPsnkSY-$DN-7DjdsLZH zHQ~SxWS1!!P<5>%E1bgEsmBXt&iLpuWr%qfDTjOv>0o7a);y}d%E4J7*nsRprD~<% zb&qa$-9V?hNsJ;BtEjsEQK(}z3jzC&eW{X_-mZaEnchewX{~{TBveyo)&~f7m<_7? z+DC|0<=3Qs?+$lnf)YX5;S9o<#{+n2&!Hb~as!7d(JRZaRG;}~*8>iEWN8@uI+)Nzdq;vYOK zG6K@T=GpUu+&7ldMM?x3HipsQlyvMO7jcNF|I80I9OV;3ot+PKBKk6kor zLGff`7a_sjf@<1dz^Fd-!KTiO1Qpl1`y*ViaqTXaDf;h=Z zAtz|_`E9#ddf*5UD*+*IVhw-i^>|DvqA=4=ExXDT2QL83g^6# zE`_RYH?q!Vqf3pfjOlnqTAp-47(aMw-Sv&EPChv}q*2M5MJQ*7G&*G+yGYCT=WtZp zEDZ>;**B+$Y-Oyd{y}j)WS*QrnIl+ni~0d9FObNp@-!N=_ywF}b9%@Xkgs!kNQ20b z*Aa7iNE54|i#W&T^pFOvDz^*|jN%|yr$XtB2Vh8v%3fa15NU*{`6a>M2TpNFdtZ8r zRcxg3@s)VY@nEa5NiV4E1wSy0jnGuhWA z*%dVhiYz%a#VbO{6;2QtZmi-JL^_P(3Do8yUK*NQM8zjIu?l)=iY$gpY*352vqFik|R*Di%X6`#V$hBXs@a2 z5Sro^zW_pD7Ox;v7q8eLajJQ!ZtB5qPf%T?MD?7`sUuyaMP;((;E_Q!MLYmGd1Oe( zsoR#bM;e`?=J1grEvkE(<3}2`sGf{ZUUFdlcPOCl*l=Tw+c*QMqNXn)nq;}`ai99ii zS3or8K#~*K10Xgwu}K9Mfxw<(gI0mDW>2vRqU!qT%*25kmfBchaw5r;sW2t`=S-4D z6{cw$E^$ai%^Lh%o2V;Qm}V}nPZSwZ=@QSRyS^4R1H`UkqZT!V{P1ZBahg7{skkJf zGP&0)B9KL`sAp4g%j|lZ6G|ZLA!GWS{GaO-2W0xLPaMEPmjg<=DyyK&XK>afs)$vn z1pi!<=(_42l`phC*CiUAqOMCc={drvtxXiVh|2TmZd~2W)XLz&%22CN=UxVMjiN}4 zn#fRLt09D&LsY09u2%y1JZ zYZMm{j;Jxa<^M}4QZiu4PKwkb2WPSm#A=5&*8UA^+ngSVV+Vv;@K z$&NYQRz~+dPAy%~(@SJhdq~q0zDW{w?K~t6S%*r_@OtxBHC>x7QHd=Go2}A>$8ybQrXPsODo+C${TtVa+v&go%#;h0B9Cgw~ zqG(2DoT7`2qD~1p>!izc3Pw?$KM$$uoVIh?Nt279V-e8n;M~G@`nWlizFHV!$EjxW$l7zc02F`!eUV@fTZ)OAbNR(4-b6 zhl`-&6&Iw*;}jPptBg@J=ny4$j88Nl<-dy0(^RfPx=jN*_oO39Qs#YTu)x6m)FVv`az zi)l_pX@sb{5=jKN*d#tlJBMx^c5%tx&g5v6B@vYjk6&DJh>BlaLZIRSUf4y5hQOIu z6C$I4UHm4wj#q_~c-NOP#Vt08tno=X9i@qN@O2QJUBxD?YayG1L9WlG7$4l?pcWSS zu#1flR;AKD{NkXCvKYojDC=vH(AisTbd?s%*r?+*lM>I^DB4zt+39Sz$^+9YJmZ!@ z+bnBX#wDz(S-o(KOAc)@j1cwb*iw377q1{~#w=bzlwf1A!9`e{Vv~wj@*h62!9`bP zmmUB&tm4&E>y1&ILM1D!U5-v^bjEAWP8rhf*Ap_Qr!=Z~CG>KBN|R2)1bL27X;kq_ zLVL5f*yN1YoT9R1Vrk4ASRU8yenN>Wk@tl?B_IzlpBCB!LjvgB-)C2N*)whBZI zi~A+Xz441*!0SeiRyl!|?}&Tk{k`SD|; z5|v-<$T=%ZENaqn4q91qh>B@kvdQ<&-r^FAnjd4zK`RjU7O^A*3YPH-5~vnGZQc^7 zjbH2%ttoY37n@X|dLhXnD-BB1ti(BHWl#c@75|N09C8U%{9>aD)CkAs;*d+Au3=P% zI#AJc9izxKP~G<#g%XuBe}CWXF7~A^k-Dalp*td#@4IZ7L)0~mlsO`GO(W!p)b)&P zAtEaCZ7t(~h`NSxK-Qo2ivkx>{TDL=`nOZmb&LEJMWbdLn+Q31g>D+1qO!d>q($|E z%pAVbsIxEgYWp*4u=e>qes6{32*D#8Vs7y!p7yC+xTCP54ur%ovm8IJ|dNT~Y zn?x>O$LP*#@Fs-WUEJCZP1i3{=FoKQB72B)@9x)Fxin=w5 zA|)!py*AO6^)?|ix7Ma?j*Srod6 zx^7YA5~%AIAqiBb$l65+fyz>&;p~Y3+3cGGS$eq7UxmnlED*L8T|>S%kONu1iKkT# zWI0L8`r$h+O*&TH>Kw?@#5(9Y+~j}ODk3AnnpHJNvUDMIbt%VC4rLis!m_O9Se8Zz z>puC8CkL|(in3Wfb2LjMl=ZdfudZAHrWTg8Sw3?-OP4}gU(2Z>2edS@wyrO&7_-2b z@5-7ZTDHt4;T+Mj#M);4%n>b12v}}-azu-Xu8dt$E}?1T84tjyj$=H4BP{2$bX6^E z>hkM3O)6gLr2=Qc#H;gZ4ru8z9j{Cib{L0ryrRMwMz`%;S#wBB7wRNTkms0|Mis9S zbu6Q+>v$z|=cJY{)bZ*Zl%rajSW|6k1e~MLIoR1bXSHmRNJ@vsGiIijDso#GeOv6 z98!|vSaVoQql=_t7rn{al9Ek7Jd5r^or0O9V;Eg%;*>^6#T^ACZ3mn~Te`ke)A5Y1 zE}F&w=hT)ha)c>cj7tbp>~v0TS#pHwSVrxmTr?fWxFAC?e(?Z^J$BI`npP?uw`g*P z>3Buz>MdYq$e2Y?G)?e~S%gH>%826@jr7m>`OmmTNG0E-?6HeRtto*ue$k}EG+AsM zqfu#E$$UJc(W$9DO|li>NWJ43QzV)a!L!2{l4X@iKbA4%h*NeLm2RDT(NrTf~!7wh_Tbdl^ zvgAm;IE^Jo>cucFIV8m|LYO+9jcGJ?@e0_L$Wbm=kO9CeHi)27$EyWav56JX*EHxq zPO(7=nm34Zn#&+pr}u zPthFgasr#*5ZlBir7B-t$iXg6Dy}Sd&UIA^Y(xK{QA!ocaDN!ADIpU?kh{}MnwKyoE z5~?}nWk^Kj5GmC+Nm1Roh|1n#7aEu3eU?9-MK@|u31XaLlNR*>@^EDa>)$F=IXvU{ z54YH*E~>J*xa3e3ySU^CRovo|Lsi`3l0#MY79nccvH6<=U#=jk;uKHNYhccMY0|3N zDCWGECYGUgSO3^sY=ElT;bkJ=6Nk9A+WYUicdVj#3??pNfgba)|~s&BypNS!z4Di#wj*&P{pbJ&L1{$Na8fs z?hl{XsNyu5;uD9IrkSH_6g37Nr{s!tiXzuIU8^XBsAZFK1WaG)5~gbv8SI2c6p-bPA3fLPRQI^+CrHDGOW1`I_Cs0dCmXTW?BpEIf73G^O9 zlU9`gVr#KUsT$z7QyR3Y&mcc<8FZ@3-r|r}l|;H$QO)k`OXJomnzX8SrW9sBA6vb^6PxDKj&H+ECTY zy`JySOkisfRn-8$WYA+ez!TeV1bl%CRgv*YDiTqdA|l>Jp=BZ^YOXT#z*ourhv$xp z%;IRWnu^Q-n3SgWV?r|#gj&;7HzQzTO=AfCUc}M1mQ}xg!DAUZ-~qo4Dqu<{gUPk z!2qnU$P9r<$EhMgWQIVbL|_EgCc3h^Lv^N5nVAMgHA5Kc^@%1`oxO_+%@_!wsqzbx zXN{ssuc*rADs&D4qN)%wtW{K@j?>I+{k#~0P!GtYhy`GU&kwI3SyE*)$xi=v}B~sJq8WbfapC=(WC>F(b0fp z2uvza8PKtbR2Efhj*L$PrMeonF^Zs))hmF0h_wg7s6sVe9j6F6(yt2jTrj%CD*K8a zIg^&vtAh%i<8TR9_7%I3RaIe`Mg11+ISjo+$ebIu=2!y%Z@;)8ftt)K!t1L~2_=!Unmz z7!my#Zf68YNt#g`t7vq|zGD?3mq;D2Xw;H2IAaz~T2gQ1s}VB~gep?`jUb^;j4pL* z%C2G$Lu<+ajbBWW_$035)pCd7jMTA9lBGMf5$C0X;H^3Hkc)K zyrM}*YA!QFAaX|PY${S&=U=AvSVhn!Qh9HY(NT4sfW5P+pE9075Yj*XV(nq>fz-ApiPD&~%-d2&$|j6;)?r zF=Y~|4C>g$kV94H@XQ3S5*Y(7GiEVAH(|LeiHuh~iS~VOKjRhoEeusQdXH5!IaM8} zNM)sJf?=#8sA84w%-$lTVm11VS2QY7qYsav5!Iw()xZ3qpQt_uLDYZrEtkk-1$~UD z`pDyxQ(wBe*3|KxQ(u}Kk(xta7ObZ7xfc~SQuRYF7ZOd=fSCts+VMKK8lZ(iePYo- zQ$0@ixJl@L0Ze#e{ZhhWdrs**m7_=ns()=#MEPLKSOZ;!F|$=1pk&1~V1eeSmqo5l z518nUz2cIRvq8lB#*R(m4nfr&;j{j;Rk#btUHt{fPKc zoi)|WW^vGuWM2rPdYe*ZF5{pqr$OcBgacpZI(3k9QB}i9EUNyZYqMWG$|O?h ztnK1Kh*ZBHsxp^hIPPgU>m_A~VW*fxs=eYjU>evdUciWIr#R#ys*U0htDvhe)b@!3 zO4O2019c!*mj^rgHT@X@w>hF#{Maf^eTbT8G%7ShphBH|6F}ZsoI;4ILX4<)7KfCm zC7tc!AVkfB9bM@hhKs27i&N$_O22*zeUKyl+AkhtmsEX&?m~!E|5)KoUoC6mX{3?s z^lP`6$_hBkl9Ox}|AjDjY!-)X)o&+6;kP;OaUn+AD6M zsLGRgorExBphBqH`PXJ~3YDt**4DC1U6bE#ydOu%fW(eFgSEoXB)LwB5U1HT{aS*Di5YpK! zE~y0Ux07yqYlYP%T1hA~+VRqYlppkq~=asNtCRU1(UX;rO|_H7Pg8I-Lo5GT6q zWy=0?zr~R*i(FkLQ;u|55>>TtMSU{{>Q7~0j4@}rOrg%eY*}!q%b+s@%h4b0ZdD0z znaKm8YGq)~b(!i;RpnKVGM#}veK^?VC=*o~A#X1pWMh?cT@Dget+!Hemtk9YGBBsQ zq{skFf@0{aLtUPzIP2UG^#^YOb?x(G75Ql}kC&Wec65%9$>M zIsrR?IMZdx8LBsLFAk~%j4$L=mmw{xUp7~L=P)3unrE+cTg%AE&BHgf`y7Uds@h#< zJtJkZta6>Tj1ZPpywYtgBgCT0d{f&@1mC!4GtEuy#?XMYUnQtz9%}1I(23=HgtbL?!>;)-bA2 ziyG6HCBMUo#DWm^U8B!e+Wo+XC7}YV23or_|@r(u) zV8qHeMuUVaPL~5+^qKnXi(A%my0ZC;@#xxpPQRQX?_4eXi>5_rRG^WTAtm7F&jto4W zG2{r=v5dMJf=)<|WjqOzdQzlqRi@XwF^n$Ku}avDUo?pdnFZcn+`4*Q&1Gf?e3^)v zMPU4*YwJjj3}YCLTGcdRW`aotDrWmOj?slmRy~2Vtm7GtT2_`d4b;)}meprDYWb9u z+pCq8<6I7~ajN^JOOcIJ-R51$#wn+{9JLXr-1F!zugho*j)he>WnaKIY91d>a!F-X z#Kb9tlU#;G);uN9{~om+p~^8XQ$&Bu!>YJ-ZDSbOnyc!V^mmC|!{#QqK9`~mV;Nma z{}|;ClcQYbS|x0r>2a3Jpjum*LLBBYq;(}GIn8Adx>opeoXe0SS~<<-0J&lHB)-ok zV|M8yD?J+97_!lNpH0S)jn=y#O6NN2Ok^dDILqZ5Jt173sEGbP{ra8uC5SsQtk7H!Vzhl*jk#k(8kQ&GmM9|G9 zqYJg9+$i10H5#>~DKzmQa!NX;kvcF; zmq@BxF5-me(I@aPmxpK;(wa;+}Q_CTrh$9^Lh6R2|(MdZhZ4 z6Q({DBqxxz@4Ua*r78h)ikeED17TU!U8WDuq`OS7tK>f(N-v45DRUWzGD<0UmDm|#h<$fRs zx-=?j^-7Lg$dlL zm35AgBVK2tF)8)Xbd4ir4o!J5t%<2UJU1X&hBkW|e1U3!)4~^Fe(+SfL1{|tYn~lN zN(9TJ6nTKwl!(lME?pa%YVvr7=0KMrsg7f+g}g*GLQ~r%$7+sr8C0TL(;Vs2q_S|H z0_9AXMjAVhkN!ica~LXAIonzlIz4tNtLlZyWz@qHtY6BpE?Z zsLDG_In+gu24``tUFyfY#@MTF2V{7`aM? z0?G6tP3j#D&%{(0Q~j9t7%5e=guVJ4>C#t93Qm0INS7g{DhV@Zx-=?TvyGoqT^g0F zKWszfSeHgED{1aA?=oiaJar@MyvZ1VtnBdTV3)2fFSz`?m_BOIF%ZY%`k40^Q`-h> zPIVz^d+Ah48rOU1Q9PXHko*3Zu6y= zK~~nSb85_?E`v&0_EmGLOQR^OKQThQ&N!%~O>V$EHbPoo%Sq^geQeayva;lCmqtkI zQZya|*~k%V!t}>LF4@I3$Ga>c5p#!}(_NOt52F(Xa*4IoizIHh+Eb~4&0;5-Uxu8w z8Hw@%HTQ9kSAy-&$u12NW0lXaj!jAk1{>qppoGm1PY!k&R2hK8iFF*(x-y`=(AcOF zF)MOTb{SH#J{TR$W225(MhElQq*aYTxW`5nuzGFCV~TxjlswGt#sl}b;0RdUCYya)^p`bc!7}oPFO1#_9LJhkU52EY*2YK(Z$P*O(H@qQCP+%6Riw2 zo^eo#8i9D9aZsrmfpCqDO4V$mJb0yXP-M+M%7a%L8ET zkrx`jNL1p5#&1Ft@j_#hiq>Rk`^H#tUB^N=D-jR8(b#1=T)8uP@Ji#Li?Y1aIH)Tm z{&}gfQ7K!wk=GgrSy?xOWQ%)jGy%&5vM?bl$R;h6*93T$~L0YFbU|1dF)cXDNNWs`5H{4OB&8mFO4o~IETG7 zs$k77^OJAFG|KXt>CG2m7Hl4V;vbiwEdJ+-eOz)V%Qs<`9LlPtHR;qa{BY996YI!i zC?GS%-b;>pIf?!q#qo^|B1AmXlW)N^spvumOk;xy)}RRoH=u$QS=o1-LXovw?a9!n zqm?+uHV#R&#^dCqmqrz>$(R_&Cdt5+Fffiy&{WqYSlM-Kat15LalsL+IL9RkR$9%T z-4n z<^~n0$?-YpWss}uiGWcY(wY+8IK@V-DFcdA98$?Qv&Re4G~%M^dPNOF$7!+!+l*b` zMbmYQjNEFvW)YADm0?)72st!mi?J_LFB`Jdz1A#J22HC!a}Ql%+IQEl-C``F#e}^4 za-Pn8_fdv+6vtr^d6JYyr2btx}6IrODbEvy;8eNN5zC0-ee zeNGM8mG!mHsUf?vzVW#n*-)40|amG%j>OI6HPDL_U8;<@Z1*!7ojaHgh=VzBDLh zW0rI9%OI3ZjOE~$ArV&Z54s7eqITewE+cF;fnVL}LZxe#^c?-tD7wab=j@jzk+oXK z&+asdstNO){?Y_dliPFn%YyYzIsIix)>+T|9R9Loy;BZd-^ z#NHbN`9(5r{Ory*aXh^7k4;L{%n&vlo0Oe7dZf?QHN^^ z%}XrmDyn*;2_zwR6i~JIy5IY>ngNool>+x+HNc`~KPjicbQ9_w3Et+;AuvlUYTlm6 zAuvlUYWASsxJcf(a4WOGdIfvqA+Mm44111WggK9aY*3=|FzAhcY|<+DD(pEnXi<|E zu#bbPI;IHraY(8weejQs5LG?(UdkK)IHcBAWzVHa@76eUvZ){T8H)#yt*aS({b0#*vvB|ltVjCA6ieej=B=s_))?`MsZeNqeFpXR0 zNWJ@dV?}AbMfu*h8w+TzJ(=(OcHEYcgQ2SQSP0l3IsavlMO7QDHpjmlU{f#0zZ}G(=J)S7{pBFLo^trhK~_}GeyI^k z>Xm!+yyoPWFK}aY_)bhJn-ZQ=9}T>It_SHxQ*sW6z6?oN-5m!U`7&TKF9YC*(~AirLx@P5Dv`L_^TaX=-yg zTuF9@IQQj3)E!jiOVnMYB9*A))R!p|RWn2QMobqORp}@{yEEuim2bohDOD2-d?Th& zt4h$>Cr%UkUX{mj9Q-n#Io3z1_!Vaq4SXX@t$623^?T}u9qv-;R99%U?RToQ-Bgs3~Yt;u-XDqg^(7rsW#Wu{1Gwl^H7NL1BV zx!h)F&>0`Hekci3%*(@I9$iFs=2RUNZUNOXT zk9*bRUt7fsu&8OkjKLNmRBaTeNQY`#rVZC26{_S?+r$BFladx~6c?4KtN=EOOFC48 zW(d@_7L|Err#OW=R9V?Swu*yVR8+869D=Cc-0(EDR~*us5_Eiv8ci7GYY-|+2K`uZ2S5=W9 zv%@xVN}Ph)B@RGPjTmFew_q3-H>)Zm8QCUonbek1woM#@oXSRtdHcjg9i$92MzOEd zdNMc5+#|0=Zpp2lwx*sv9Q-ncO4Iv|makP*AvD!w^u+#KugJiSq@4UR zwVgrA(Ju$V_wtSeC%+tICFSIogB+6bgFA@LlN|LfzL#@fQe*+0(MZ(@6wrMuOXlC& zM3HD(L%l9hmew1;M0qES^B5!NOe)f_=?er>0_Q-P{)dQ;zI1Qn{p zTl%a~6uOAY*QdL_4pb5!N59MfwWwTat)iQ^amvXrQ{*D*T1AQ+qOMbf9HOpMgdC!- zQFPuu;&grDfXqX0C#Gcs5~qA|ngI}~I3-d2c~zMrXPmA>bY0Oj8SHN@qEW>u(X$rO zBypN8+Q0RPLYL(Gw}!j_(Jxc# zR8@a%uXJW#j($0ctVQ};r|9k;w=*35a+En##VC5;PdmPtX7LI#N3n`m z5P@)t4Wg+A*ip}(Vv}gfR>>cGiVaFr{iX?JoD>J0nqm}(w5BOE6G6SXXo^$pLbIf* zbxyU%mC)3mX=>~LNt0>B8LC1P4l07G+4U9iJwspW2vrf^G%T4=6|rAlGNCGR8HY{3 zB9ju3ZbTKCVNjb_Q~}>MaG3(N?ciOle<1|1|H45^2L+_SjDRYuNLBm@%mDZ*wW?-7 z`JJ6Zr|C( z#S^IW4m@3E#$bz}9cHnMBvxz6doE>)sK}`*Rm6sOay&=YHC$x6pr+( zZ(+_fpva_Ok;^EYn&KF{ZO8BKtIP}nH&t#~I*MJSNC7q0i>ivu5Nr`5h&{zFQkwdH zLPcf(RHQUz8nUO@MLJTQfAzi583A7=k?QqKWJbWGA~m6aSM2&OnzE}nqyv?Sux3&9 zb)Y7^*DeZ;s0_mzMj=FX4Ab4UjBcR+Q<1uk5#an=@wk=|l1OD#{pZXCh!@o#e}PY5 zy1k4;e)!qn(i54P!0nEERROt%u}JH^c5y)R;<`nFsLBedtIR|YIU|)#MaJ57CB0S9 ztywgRs=o18Z4(M69jSE3A7lvhZ>6fefW8kiLtxU8iau)>g*s9hxAlu6C2FPfHH<&Efz#((f8Z$T%gHxeUYp@V>r%IHSOQkVhgpZtda%LTJt6 z0!CoHqChmQ`LSM6#3JOCcwK#RqU$P6z1Jo*^T5c}sZbg)17K2->Sv!SG$}y{P4gbT z$c%tVMQS2{&7u&Rst_T!Zc(HI)#hB^&75EmLQ@rDUaesiv7{Q&fXRx0!zZm_gdEwI zjYTFs%TwV_EnoT2I|=fXtF-~7jSnk^|udjN~$UkQeo;| z>TcrzoqTnBW$XdWynN%2-{PDX4c^0#AwgI_L0MI5KU`vW^F5>exllnsx{tb&SA7q2VMRoYrIMR;Fv zkgL;M)?A186^FE__AZ?JGHpXt6=M3>C@v{c{mO$1onwHgd7oS49E28?5dP(T#co84 zn)$qT zUR68^@$BK@RmCPPD-zACioQ-_wIrHvN1Jr8X5ci83i57T1uM>zqhA(vh0fZ)^S+`h zL|7XO|gd^w5B z&^EsK(&VBoZ!9)Jidwul|7BayF-xZ7oy9H`zoaWM-n_LqC_&5XYI-%!!_X+1Sfv;t zUR)fMsO4(`Ds>J-Qn5;rRd{o;OGR5Do19ru7q>_vmd^6>;*uj9 z^YY@7BOCMfqGC}wNnO*(>2IYn55gB9uk!BV6<|Yud3CWtg=^MC+s3KOBIrxST*R(% zz+_^Ai!Zx$ZA7aI5yyPjWk@ttfOsbIU6)2^s_qcsHjZ;lUvFb7#lIR)k*E2+*3?FQhH zk<{Jc444!t;8r^Ea0JYSs54GhoB%VRQ?OOw0GJ`sl!?RnF9TXr_8~X`W|6C_iNFCc zOQI>iP^Akw5*Salx;6Doig|2uYO3~Z zk%TF)+v^s6rU8S!$ybM|T*_o&Wwh~*dl?p1|2m~=^B|CxE5+Imgv`RQ1b@L1FyCc} zM~;9Q(viwUz&Q4GEQ8%ji1R^{mNk<^<9i_2=hoT&a0bklDqV?H{oeUe3SE6IhcTzX zOeuoZ719jcV>bg~U5e4eJ~nD)332^a@o}xnz)1}Hk>CSaTGz+OVj#P|);5i}j0JZ& z*?ZFOm|ki-;+1c^Y;8xp?$c?fR*hcoFy$y1cU|`e5Y08Zy3eKuu%Vc9V7f%dD`UD3 zrd1@$vVopcV7kZ%%LwdaX4)bvxKn6yF!N*| zOsl?<)$jAFkd}xk)T(Am?kkKcgsP533MnY2P()=D^0qH9y50Lz?YfTU!BOPM!TbIq zWe!dE{YA*3sbi5Z@3K%xQ@!2eca8Pthi91Xxon`()WnOSe)ZsgL695E*kzDGg;5p9 z*aTHY7&i=K6J+&;&;iFdsOv+A`L;Bz>k3J!c*ZV;uF6yq0@FB%n;eyAqTm`EA*@T$ zD{NyUl(q8@I)@_43V9*tESO{bG@4p*=@!nh>nm-s8Jq@lltEi|(>V?1Akh{z=G)SY zL^cnTJ=ZvvWzwd6v|Zy^WVH1f5O;VGy1Kqmq$2i(7HbgBnRO0D zH_sBTxubvN0jbvRSFje}IWv_eT!^Pejxv+v#c2Ng^z7z^RaY16j=% z>SXNvkON`#ska4NS6(*M|7bQ$q0*IC)`U7sx>V_!XIa{8K1y{qc9PN->_IH66QF@Q zxEDHCSZ&u`$hxPT3DfO9pSI>mm?bN#c1Cnb#j&3IMpmz|G{ENR3;25%YMLYBO316a zJ3PQeRDPVXi&S_Kc^n3_l~ufYipXFewN<$0Hb)-zL{*;yS#IzklZhd|pXw&`3hO+~ zaWF$V4}1P{9?XzlVBgFU_8z;s3RQBqBtI@>Nj0J=Vh`O#dVx&~Y?_uNQYoY6oWzkD zJ0%$|1WB<4YPJabi%w=(&bw2E{_kIb7Y^Lqt!c@)CZkJP%XM{A> z-=bxFL;4CbJnT1KL86rH#s(Kn*=}rN5p)qmV7IXWn&xzI%@5~-A+Akj5Zk74NyTX7 zVYjghp=n+;RCNx7A$Ciuj+uySY=WdB$cAfda#a-D*a$^+A#}qwHnE@rsm+sM!67KF zamgVlrg6z3D4uc2A?RAh&AZ$`+}AM{@z0eoaU9NZWJ@xl>la0$X?9!p2pH#g0!xN0 zwK*H$30+$H_2j&p2af!99WQuECg0{HeJEn0-w57Xv#wBQ* zImBk8SKD4wmD(9{4C58B`*X)GUP1NK0ny+Rta!yH)gz^6+#}o;t&U`E`)huudxZ5stmfZ*VyD5toxK&jjr^oNObz$=hQ-$ zRDE?5H2TNz)RIaQG+i$wih6u<5==LtToR4TNia*E9v)AeJqJd|Qj>klB<(3M3-oFQ zMaCpek!teT1A$Y~2hXT+0V1V=2W=04;VR{kn9_R$45+HJ*n0#FB-O=Y?-?*g`bX_Z zobDMgkZcBJ$eBK;z%(gMl|w=2D3qppBnUdk!J3v$$~iDy-9=Nj8kZcJ zsuN|C{BJx#Ph{s9m@TtWnp0r(%S^MRYQvRD#a81Npf~I^egTJ14uENJ(G;85#3Jb4 z3Qci|4Tw?=X9kUp#zC%5Eo01hKs}_U$ea44h@SXVZ_reQoDXvV%n)m;HXs~Eu?dpa zo^uX>X@aCGLs!{kY=WXHliKVRn^;g4VLaJkT(C>(1FyJbm(&MVamg;JIsIkHE~z>E zh45t(Oh$a*6t95y_#XJgD~LL{#0D2ZF^NsAfaVt3U=bTs+Qo7{@Q6cPoAL^&h^Nv+ zQd^Y9BQ`-!l|cYJ;*d*-N{(7ARN_p#4_smwx}@C)KCuyk>PqN_QEYN%U3}t#Lr{$3 zl0#68;*vv9jN+0*P<$e>>)EYwQ;0Bb*u*Pf9ev;uuOM>}li1)QC?2s%LMq~55gSy5 zRxV^~aZpvqEWjiVxkM-~u@RE0JH#$7aY!UhwBZt)ps2EB%FHdJJ|0CK@3_P+Q#qHH zmL9moCTGg!S@eQKP##7vNy_z_{Jn?mmp>!H)&i5>9Pi4U{mnsWno+ zNng9ASVSclf!$FQhz8o3ab^CCQ&}{SAAqRWLptlSrI^!RrUj`l&)pxpr|6n%jΜ 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 i$CEniOOHbyUUUQ literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/SoundSe.csv b/titles/sao/data/1/SoundSe.csv new file mode 100644 index 0000000000000000000000000000000000000000..30a169904bca1cbc72961e6503ec44a6dfb749a3 GIT binary patch literal 146198 zcmb`w+j6AIjV5?6GrhxDUgy-r`EVH|gJg(7Mko%M%DM?FlX9)gMk}?I)U%^^+DF)T z+E-Y>9dQ5P2mEAeTh?XlF5wHfI{=5{^#70l@!!sW|F6IO<@|m3m(Qo~fByS#f1yAA z_+Q`Oe)&JYf4=`O{(pb@`(OWy{*Q<6fBxlPufP6H{`lp8e?I>9>)(I-^`HOR{pI`L z{`u?w@$2{BKA->Rum5@ZmHykie_a3Z{rJn@=#Ty1|Nfu9|J`{0`O|rO+dO=^Jsu91 z+uPyvy4ipF{OKRxKYgBj9)F&UK2PSKNA%a_xBvPc{y|g=OLZoi{q4(Pe|_C1+R;z* z4)wA{ecHS|W#ZlsuPo_)bH3a*r_=6BgXwX>^xD#1Y2C-oTjCdg^6{6y{zDf_J5X%V zjY;>1!?8&l_9+1T{&$l}puM~7Z}-n9`d?V&csxz}n^;x4siAu~d{sWjt3J`p5It=# zSLJZL?o&kxP!^xAo72PXesj5h*46RhM=_|_jB;RUUk{hvLD`)!NryO!ffB!N?zgw+ z-Q_kK>knu7tA04jK}|bRT4!&g9MrT^rJZ$oo<>imwj?8PLcfU=CUP`Bn=XP@z`0om95vq7t#Rz;l4;tt#Adhlalpi za%$!3@OBGpJsx&%7gW44;rGOAYQ^jPvLh39cYS=6pQoejl3MACq@=s2WT2*fv7TD- zio{!8^t3P2Gb>w>@ae4V_62%o1uGIB&#Fe#{;AHaSSh6{k+ZuqD^Zd1k7{n{{Cayk zvw{=}Pj|Y?z6{T-C`H2aU0!Bi3TIZF5@}jzUlM196w!tAY^@X>TX(I69A#qtWjeYp zaUn;USnU*@UXOTQ$WkCShKdfbOI*m)al1JcYwnBGyb!5OoYvge*?A#VnK-SvFIn?K zvI4PPb6>jVg>+rED$BFJT+OpwJ#04*x4YAJ^CFTwTYnz?v`$yw=%;CPpyYy|XSvFx zx4Xkx^_@;|{F!BI#D7%eS+WB8e)sgOW_-^7b#=Ys-{_Levs`6T^UEw&@XK?<_##Wz z_U-j{w>^Bl?jLWP%Vl%_a*NxYuW&k0z!D!)wqTLP%t6wFr_=2tWtnr3W^yug)P1BZ zfDY39_P9H#uS!DDhsz>~aNy379Q3KPOd>VC*4@jos2p;r4Q0EVFOw zmR9T@_otp3j3<4wwX`DFK+#o<>DkrWwxyLcMuCGKkNcKyX{9Vk7wmZ7STC)7386B( zaCo(%_4Ksw?wj-4Sy~}$peQS2=k>F+;>IYrZ}f8M?fA+H+|yydC3DDXQX0WUfAd#X z`Whg*2-2x8Csqz42&`h!cW+i!_?}L?$4BzEJJy_ZtjmLyzYdTthLo-gREVQ+1j`|{ z>k=0tDa2oQZ%<m7tGZarzJjc+fC}w?^Y#E| z+EMSgjUY6S!sNI zeJKSgAAVmm)>bMTD5}`wNnbPAljT4f2T%BFXBaq%F@+g>doUhZ@ryfXf<`qWkc zfm-=qd2Cw|Jnwg}Tf_LMFZ$z=6~J?T(dfpnyk%Pv1nR`N_uES$N1ye_Io_-CpAk-ndwnwUay;@ZN}2rY;qaip zPAl@rvn-vTvp6_{FHwHh*FeFJvHX@Jv*c>(D$j87I&w-*UZyzEwUU3wksEUAEEWEm zBQp}t-#$Lx97VBQSK;@((g0KmSbRw0{NgrE19X;^@4(S}}>Ta{hJ> zPxIU6$d*HPf5qEE=9E%1{11k~7d{(d{z)e0tOIR~M)Plz(Re*t{O2$K{PTZ&qO1l%9*kJv7H3(o@guzB4+Z8PWOh5Dpf$jIB?AJkDNhtsFa z0`|Du?jLSX*WCjpGTB*IA)T@r@Yv3B_C##oNJ>=2mgI^1v{}gb>bX$p?|dbzaCi6< zEPiO;var=4dU)i+x|~n|MLqQ!BJ;X^yWS4Rt!C{)xWFUL()PML zpDEpUyJrg_8&96I&vBMEC4b!P_S<-q1sgRUiH!B`in zm5f5(sM~0sXlDbo*|z=hx}}W05tacZm4pKSa3s#hdmGWxCqwjE*ka=6?s~b9z#i!# zrwf=2NQtBtm*%`T|{X@j8lcdzOHkMi)gYC8`z&mI71W4C#y_pYGc0V=pk}L6I=0!)vc4vcQaD zo!1^Q-L=;ir@)M2UShgyuWfmO8BJ)38E?RjEY*%&fm!O&d~iSMph z;k6Ixk>pb{pApmLb4ZUUHVJ&L>3@dTKBPw!bZlU**U=+r-W?%{=E)~TB^WclA76H- z-Q~cKSoN_+LvK*YoJ4whOucPx?CPD594^l&wd$GJhh$J93fEKE&|be zZ%09k#OH5s6gx`so>2(l+T*vxQKBO8=3#TZZ0`2-9?x;BXr49pRBNY&i$*!ZJ-l+n zjf5c-nShKg<&iiPn4wo-7EMAUiE_Y(Sc`TzvG#Q%!KJvs#M97jN7HLy0%NW9)iwfm zi@a#NzT@?6cYc;&#z*#@G{22>+n(@!{osc>)7Qd(H9M1RKx9x3AHQEpGm>&wgO(^}-1Bt#4nT_#) z@ta8M#c*;*Y3Ec7O%T52nNXsn;v+x+7rynGP`;!Mk?dcrrEs=XQZi;C51XeaDmBKn z_&PR`AU-@E52OVql0B*rfQJDkfJ{=B+0A7`?iEPN zK*V=~DJ8X(FM3a@~cjpdkV`^f5mO5Nr2@* zRCGe&X0~HW9a1PcIQ!}Il05p+Pb3oEsN#ec%n)}ZjmHi7>Xc8v>>j_V%eo$rZRryU z>5B`OWl(KK%26*v(PjQ|emEFQAsr%tI_91X{fYuYemr(U2_|MgAl!4B6SAD|A&~1bW%`haErj^xT&N36zO6kpu*)F`!+)l?VftNb)`b;clCX{a2xw%UgT2IDxKA zM?{JDmmUpFApf#Ea|0G?%!)$8K0ONDx4V?CTrCDPo!SwSq@K5XO{K4>`q%wIN&5tb zFW1ZAL^)bA5s&&XTv6TMfRa!@Li*|Z!|V2r8U;kyR-y9G*=|Yx#O(={}?{xF*$ue9hA`QKm}1et(053h9f{Qtl&hOKV)Ul3bQWY>1P=Bt*|FOM1&<{(Ly_ z&Kh^afDU-mlB*)QB$5B{4NF#uRX~8dBKse`VWo`)h(B<{Qqg@{ zIz5KyZvVgyE2${><&WL4($o^@f9!^p-j;yyV>hhywFHD8xGANfC6NEfO(|_Hf&Ts@ zHzi$3d~n~cB<&A$*+B%80y*meyDnWRiCXWPPoJk$(L`3E?^a1GN+NxPxHm`mT;-Rf zOCm;N$G<(G=8|7YL*3gFTNALIL5+vIF z_VFVAgoPN8H%WKIy(ftMjzXA6s(;#1jg5F&_!x$+uJs?UjmhaZCWg(<; zCN94wIhHe9g|0L2+{NFbAK(7z2$ahK{hdn6g4xCw&^UC&_kVFJ2@0lr+@W|4RQgRR z0@GCW4%p?@**h}) zelsdM^NjCQ(gaVGAl^`oILR&*i^s#`n(!QnoT7HA82Vl(T0Lim($_ldap}gk(nC(L`PR!a<#Y{g|2Shi1l84egjGe!=h@Gw3`D-!l|SJ_ylW|w6g>KAX6M8Rpv5=E@BDkbDEO5 z_#p!#L4Cz&8j;aYo>KzN9L$A;U7#C;O^N@Qoyx2zK zA2Tcx^S4ZVz_>umM_}Pc4U8lJl#dS@8R-9DE+TbOjUhuL2>|=sNhOAi4fK8RZ~v&l zkzl_v@#97Z`ahVTTG`)icx3*RvQ#6b9Ud}1(pl0+O1nK|fTYDlk*aX-kP(tFz0$KY z_Hd#!4J|G>0-;@KXgLy@R}juhQcjxEu0-;CQ9Ftw&&=v^-%f5klK`aGHzVjHh)@xwr=Pq#mncZsIU3*Q14gr46oPyWk-b^uar~Qj4r>L212+~ zNi#(9P;S0`3x+Fj!jbpR%P0MQ^k*XgX&_bMV6Xdhp!;_!=?b8y7kv70Kjb=j*lXcWT&YC z`|X-@{8W=c?<-dVBz++uKN3ToN*Y5TC}B#_)Xe#6|mqW@c z*$knlaoR5w)C;?j5Ue=V!QiR%a0tf7BiU1F;;;ZIG&vN^o=Q4H$UhiTmNbSCf3%~Y z^K%@85OTJo^!Md@lJX92ll8M%oMi)q7|@cvUk&uJvz;ht)3D)H7$E1EL&;qT=O2#Z zPbGPwE6*X+RTv%T{c^NkLir<6JV{?v2xte!Lt#A1%EIPlHa-x?lNndSM=&M-?T((o zWJ^8}Q=Um$Ltr$vu0lX}VJ8?917S1CZX_;3N(Ca~t1$eoe;_l-atHt$0S@h_VB{*y zzw_vg7$*Ak8ldaaGo~|{cZI*73QD>;)2N(GF(o8xjY8)AZo<{!0 zcMYfMu~@pc?lVc3)QEI((_A_68^aoXOE;6$NkY2pE>zU~(FI0XD?r}2I^H{sqRynL zq5vCOSZkA_Nef4x1t^}F4T19>Ssr2-JCL&Yz?_RnZ+*dCBI}el?|CZR60S$)SWa znbG)c(G9eAtOXf!bR{I;OH^f{nYqedAnQR7a6f*Ve)o8H>YctMw_QP;&iw2YGV8{)Lk7FmD9EFPZOsamM^)Sl2K{`Ia9A;xP~mB_A+QJUD7*E#15AO*Q6wU5+W=2hdm9+-S88d3T5vO ztbsug)mgC1Br8Dw=_Zk8lF(_vMc<)-vl?9=LzHcS+frKjiQd)pwk*3)^ zBU z63Gd9u+4+9@?0$)hKAA7o7~2DZ?U7+Le#MF<>YZYYbf8q6v7##rHEVBlOp1#yE)Ks zGK1U{0g+q$aA2k3*Fo2M^CBIMfn-$)DZMOhO&q8um_Y)oi`zQ+f$+o(Qd)!?a~Rxv z&831}O&W+<%pl8Efl}Q%Z750VsTRkwTJQ6S+nTM<;p1Am`2P1_?ZJ6D|f$C7ThG-gklX^hW(Cbc}udg)L zNOx-Yz%ZsTjeGLCOi3-0FlDg_mfNj#an1yKE?whjg7U?G(Z1tGeJAZaQEczQZ; zK>IDTAL{CQziy?Ii9;^xw7aKDe5y<3KOQt1Fs1NsEC`4?Jc>Fjp%h6(AtO{C#bv6L zbjTm5n}!;@9zRw(nI96O4c!1Hw9E0|SXN*dQQt$7OUTJr{$3(}E@3LWQ7&A!}DYeMY?y`M7i&>%?MUQ=J6rWDq>`g_xFA#I0G2o5_N~%aXhzRNX6EOtl zimw2OWuhdXrf{5zA!mVPb2Pqv)o&bz zBT8s7pwmXP3Iu2qa;HuQtg1~fImcYZm(g#tMjNSYs(_;imn-tn0<{_t)8|NcM!ca$+^J|s$d z2+`-P5#C821)?ICi!}VYBMS2Amx??(O3~YLgj&7<6Uf7veUZ>P|89)I_m&+6C;c!I zQi@OPfAg@Tl}VHjuUuo76?&wxzR(d7KGM^IA->qS1eoxRm@W*v+g;I0^%2;HTSRy@>1$_(KZGnf7#AjPk+1#0%2 zIX0{Xa#CLW09wd^gPQ>2;20uZ=b zmR@ifA}oRvK*2=`Ct1NrdI@0ua6Mn1_|tq@ukrk9glN+Mfx5lF$s7{i28jZ6em}|* zP~$Rg`!8stlfuxM`m2i8u8&G=efr0DEAss5LLW7nFh=Fimr=lh;&(D3tAJuN-uvv< zT!w@dIHwj%$uu=$!gCq+#W=w*Z1d}rlcCA$2yLfP4NAvqF3BE?K^2n_^T#BM!wsL+ zAc)qPXc9#rXvTD?-#g#$D8Qsaz5rk(Fn+6+`6 zrVoxbxs*41ILYef)y|*-@opCrT}?nqp|wOq8-(;MR?FZ^+}dl7Q^ky#sF!Rl@<rvdi z^O1WHK%gymcpUXwqm!A|x}L>4uiJ6A-=~o%3;iUy2uUHxlB!V<{uj z;rKn37dRe6n@d+2w690D>-_CRF)_La(sw|E@FpyDJSvv7pGUB$&ha7CK)hjjRDyDh zktB34gMMF6nP-xt0C@b-t%5FfQk5xlhqyJ^I z-@ef&8Ka9vI}NMCYrn>cZzkO!W+jdZZ!qL)vE+UfXnTFS(EyxRs<$*9QyTGNZ$Ae` zYPReJKrw3`zIwOa@3y$xipKY&qA_xzFw#Jzys=MxJJXZqg+rnHD{h)1^22+c9-0-R zWQr7VzdPUl5C@LNy`LtH$&4|R1!IygQkbU$rA#SlMOB8ka~}CgXQ|WULtH^u+5(Jk zF9jsZ1v#r!Bus6Q{IEG>y{7kse*chlkz!5cuQWDKT9*tYH?W<*Qb{dHi8_bDg^pID zg=R%RFvw{4oa=0%Wi4o-SuF@?kF4LEKevlbZdsVv+~?2gLqMeO#E^4#$DbOpm}vt! z7|@5bHot^429o||+Q)1{S}V5-5!3?*ouQ5*wdE$vPQ8@;bsz$LWa}`mNY?FZ_vp}^=ga!O5RJPAGRH-QA@r}ir z?DIErXU@h*C*HE7RRflKB7o!^X;oGbz=Xx}r>D^{T#{U}YrUN4rnb%v?3{h3Rkl@{ zt!PRQO{=B3hgjc4pGxUFehbO{DCPa(?ePlLJVcy#x_r7UB3-VmznypIaMB!gapD{c$r33sC(zz>iZvYseNmuz*krk@mk@2VVm(slv-a&E zQuM~NmEu#i&iF#6&nXhe0aKQpOqL36m|znvQwt=Dnu6R>w&~%x2r>Db6Af*vX{ljsnjKi!_A#rqJ;>s8-iL2!*IC6dUl`I!> z6%%FUDidpvxo5R17?X5Yjpwdab*lwvg_>6y87$VD`O9};3(m@ShC7nZUJmI#q$^s_ z6`vEu^+g2{cntI+-k0J5E|;#dVL_xfOSdfml{=P@Tze>OSq8~&Rl=dm&tu6-zSgHy zTh&PsvlZTM4Z9+&( ziEfBbpDiuZw;d55A&Xl^C>Fu4h_{F=V;RD%BBrJK`E+pQNb+734v8e`N{E(2(#NoJscf_yl0Jr&OvPw8Bz*`g zoC?u)Nct#NKozCskn}OEgepeEA?ZU{8RZMCdsS0W7K=&OC`%4b&zP}5Qd=&G119M= z1WO`YLY2XS!pcjcj0YaRZk~1&ZKkjnC64H2AQ^BfdZFnZ&g~^lNll2D`DO_BCY4!v z8G_ZPu#{5znTz<3yLCp3yETvA;NT&iVgRfC+$dWjLZj_B#@QD9!Q!CFyat% zdnmLM9!4~dZH&Z6XgtqbTJRx>t&9?_{|P(JW?K8mO%ARqE~WS6@mc)7szmmmUpa z{_UXQY&)`o`|hdqC$$jL-kECK%4?E5R7ME;UGU(#k&<_6A!HuheUy|_Mu^&7Fk!c{ zPzdQ$YC>c#tQ3_H@ty{Vpxc&$~kKgCp z)o@80^|4*HU1^t2r6zRnu?P5e^*}n79B`L$YqCl~DXl-q_w!-@ZGdlU%S!B0`$}w& zZC|k^ht=GYaDTaC;;Q^ovRr*^mu*iimy!d^(09AjL(iB{9!RStylq}edaMiTvg;}1 zQgUZ4s24yW!qE6r6kvy(1Ru5*P{0;*W z#$flkE)_A0UBvd-HpRN7=eCr2i!HW?&4+2KtDq$z*oAhPcGXaFg)QV^6&y!L^c%j;e`%>D}kk~}EP~1z>cZI=N$=noF#$7xh$(1v}rVdEhSUNnAAof2fwFD zSM+As?~Y|TV=ifOPhZk!T~rD|_mN#OkbXB8xl{dKG#!+in*}y+1Pz@&)5RLUZ59jF zXlt>u%M~u^gtN?s@hLIrkRo$Dr#fE%^}%N$&AcIwMnM=S=O^fIQ(U!_k6AH4Ib9re z{r-BS9)z;GR>hJo2!}n%uC-wKC^lhA-!2S$Y_=uPM<_k?Z0bUNZTvdneiv$>&qw-X z4b+Mz`9!a!+E3|mV}@(U+=2n;T&7nF;gN2)HlTC|@4v(kOG%5fx8f{{HG2sSuAo#? zJsvoyY(pJjxQ*TooCVYYhKmV+(c!93DjU)oI8>HXzqRbERZQ1DOPa2IUJ9Ew805fZ zR`PW|YTDg*qh`mUzXfS)0o654kPxg@{@%nfW#nmyM!9_Xka(j(}BLMll@RSsrj9=UpXbl&lE zTDMqO&Nnq>v-Ry&ojN2UYv{0}J!GHbdv& zbSVrfS5xQ~uwlVc@FnCUD^&6MdpnSC{~FZ_eqR3{>GRnykLD3k9vS<**fpklv)T2&Q;@eCgT!$-3E+$+k1FfQCoOfP+NHRgc6Ifj4d8c0~B(V0{ZoN zciJBhbT()D^5glrXaP))TK3TyR#a8ukJ7pZOK+my&eR5ioxsX9d)F#a4fL!6b|bc% z`)7hAo;R15+vE21<#5U}kT?e!XzfULF2?k#CdH+psuWGcafeWLilMf&4KnC(2^qd` z*SAf0oCks0??9l&>5=04QZhe~H#IV5xS~RPN@6&V2uQ6N@~GEL?)ir+$etPUctbLI zwz2g^yMhdvA&(9flV^Eqk>?{9^5Re!!P~tR zMt|KkmvrHBTB!nl+ERb?;o8G=ZK#YttT1U(5H(w0&)Z#`=5MAvnZ0mrtfZtG@B}`v z$!@rEB_9&2kWZT(HDdp~Nso-&wHvos1J!K{Kr-CiWB$P6hRUpIl>24d$97G%^tIJ2 zla6;Kq9`*il!}_Do9t3TfX2q0m{+wp4QX>*hF8-0RNzKu+&8`wlBV3KW zeuJlQ>9e_*t?0nwP=c!Z1guW`y&V2vnypoZXT_klG2#H^w9#Va+ zh3kmEzYuuJ?jWCm2h_ROxJ^XB&Xh0LQ-Nm(?Rn1YG)@{)gKs;k4fu9dUo{5D*-vzJ zQ@%;YUS0v9`!w)ifJ9;ekhjOZ?jUDa0Ay#`)gwp^CIEGZlH3eD8>mx~b}8veydUo4LB0#t#(3xR0Wya5THg79AVux&=_M7hP}r#dnq5iz zU2`p$Q&1RsK~R;qhmjwZ)mms0OHpRxzAhnwSVK{yTP0<#UnFXKG>|rI!`x_gO0855YqS*dvE=`4aU){jTERYitXmH2orIsvN8zb}K zDl@G#IpFz3idf&+S7Q0LSi#38ok$_wHr2I~1X@9Td8YCs3Q5zux%@mRWqUL!>o2V- zv<5hqJ2aECJn018*^=v~B)7ohLC{+O__o(s0WE!i-i8R@)1bW;+Fk=CbTqf`fQ~lT zLKl&b5<8pB)8@DfQzRjkFzAIt?#$AwBo8>@@*y(5E-iiBCH{R^T%g;}((Cmz5Vwbf zn$jolIX^^P*9y{T6-3?z*HS~XUj>mj!Zp&6C#xXxUbto&l42Ev-oOLFd+5-sAoSv$ zQI^5EZJ-gagg9Q*2O}@bgUE@H5+X;L$+INi(L>1&Y70e=c1~`ihmxxoN|aG@DajZ1 zAabo*h6o)PX*=B86Y_GI{%&*sLSHqFL#25yJkeN7axIu3!ccE$dP_&*$@f~4YbE)N zUJL5V_&*N;C;0ifj{oK~umVwvp}&`F$+DH4&=mIgF%@!#T&Y+~`&a=m=4S>~A%qI& zOBx$a!7rB?xW@Cc$S-R`L@uR7S_7m)@p;rM2NwYv~16EiXm4us^(nhQ~p@Gr-;q{o^NDiQwWxRw-H3*wLI zax}wmD}y1qE(?*e7<3c3nb)=Cv@AJ)Rw#^b*9*zb0veg|N%#)y{`!WMmA;>#i!;tC}5QcP7XP6#tM6^JT-$cK2F3)gH0 zk!jA=SCue_;o2ok*K}dyLc@zJmH4?$d~5mO7%SK$ly?>P@Z5b6t>s%|6}j&C+^u6; zLs};Ox^{2qcM8Zr_=XblG8SP=Em~Qa`FpO;nL%=!)y zULc(AP`JF8-5PQ;4G=w%xm$0zmf?t?Xy;rEz*^#7UyzTrWOv5K0is7KKW$t)CowGR1nI=fKotHR0Z))t$?&h1@TU` zkS~O>AXE_V)C)+0R1oh}3}|F3AAPOE2>1wuYKA%52Gojj1kmDLk6^W9tgZji4%=|jBixF^@rWdM!v*A zp-QORu9F9A%z^;U^me#AoDR21o~~YuAS!$!OolXYrt%FS;-?rlRN}x36>w%Zst%$L zU!SkCJu}~5!JG)d%qgBrPVn_cu^D}t%*@BbtCtC8MgVDXd--;K*^zS5ca>wQ39ro+ zWlWF&)bjSS-Q7P+!3v|eRu)qv8HK(|^FosjzvcaLQM4kv7@2*V0)O zoaqc=UraOd933&1QHMJl(Ue@l zNQR$WNRJPAo>Xf|mcN*}Q}U4`#5DG6!(ZU~K<;D>xeG&{cENW*Aa!BL(+2nt1xQ;M z^0apUp#Uk1NZxoT_{JGB78ap+D9|nMoFgPHS}3}gbGqm~lqgYrhqq94OXqagdnh@X z7EwO@P=HB+C4#PGz$b!%fF**iV&I_wJwYWxmJRpDlut+H-OT&&PKO9m79|hw^oO9E zC?UkDRjIOW?%w<35p)!l5PBGM*P|kcTq%%s@d=I(2XfO3c$f!4;&oEH@p_A;R!yZp3$dAr{8r!YTspAbTpwgN$q z*AMs;LKe0HAsY|R%OHd-X9Yqw9-cubge+o;(70P%ADKY1jFlk8^4`X9RJSg-wByNl3QMuNo$hoDr!(bV>eIL%m)NiA`YuU52GKGkW ziJ_%Ekt|DyOQ=1uAu9KMNLO0K2YUb@HDVN{l0LGBBan8`ACzu$cQsKuB3 z2s44?S%JM*Wf<_U(NrYQs_WZ&$$*rt5cu1KvP;@Vc z>^o#m8YsG@L&hDlCjljSGoc_pzdraigR@5{!I({8piBni-FE_HO9}+tgP{unG9?9q zu7Bt@fJ{k&kfqAM3ZR*#KzQ#x0GW~k;k}mtWJ)Z;yKexo+vOg^-&7>JM9dq}w$3>ZB;A29aPU=m_q5hHZvjb^vY z^>laLZ{N7pv5X=4U4xYTj+g7ZP*IU>rF40{Np8hQg~88M>b8)<&*Y%`*phVdu_10u z?y48d8;>LfQvuFz58KTH4F-)DL%5SE7mvL9#zYb`ePHG?e66pA?FE;K>3H=IV297> zHNXd+Et1Qrz*n~?N?7daZP`A%!J~_wX@$F{P1yX*(ND!4c2KcH2GoJRTmE=Dyj_Zk z)8FX_Zzm>WZ%=m!>>w8Kv(XN7>k^{T_+B+n7PAKsTnI+tS*==)A z-S%lKt;qUL6=sJ!9TKG_lq?W?xfMoQhdT&@_{gqqg`uv|e89-TbUDKK zxO)fRx-#^-h9Tz;zI0{imH#2v4#v$H`u93^@R2J+|6aEaM#vfZ_d0dZ>Vou_^zJM^ zx>U*$w7MWY4VF$G`h37(K*p$q&{LdKP{vb7$QlX6c|^tH`7`BncaX<;1bL$bK;`pk zI0M;zw*;RM@-sdmtZo--8YAHdOjm z!p*=q!KI@n-%tS>Y%SMH4jioHeJlu%9N%x^=cpND;AAE9V==yjfK6g!Ktv_=VFS!&L?d$*LpEaCg9*!h7eB;p;erU zC%00Cuo~jaAz~T`ccO0FB!I3#97DLu7S%MxxC4DHXLH#dw9IPm zwk?n-l~$Il)S19|%S}TFMddtcf+Ub(igD@%y6@^S4s(lO;YxNas;z|Ln{jNGsZdl~3B^}eoWF)BWrvCXo*HAvObr8T@?{WvW)Q}P zzGzYYNn_Vc4FlZx!Y7%W;BM3~u*I$;IL5A-8b)tST9kiP;0QE;iM1@e+S_sU_eeGWp*295!8It=g>kCOP8bDa5({TMPkfdTsZ-dFqkzBPlq4%0Ux z2Lr|f(mEC(ZQ+2~fUJ%IIP0vang8e~Boyiup=GtY{|++)$bBph6PH zqE2~a$J+y2FFIKq=bGeV2pbf_V$bMm-M($;!-~cC8ezX#*&MzhA=ASab#9&}dvie}5zSbzsl$pP=_BR#6=6JPrIkX#lNve`*@$P3CJCTRr)u^Y)8r^Vk~E;Jvg zd7S>s4jYv$KHc^I_U81|^GxIAxBs^GV|$a~-NM%fKt|Yq|K+#8{_E4{cmoA1&^iHZ zfHJ>AGlb}-guFo^IRkf)q{r~SjEWR+X;>qEdx;EyWqJs_r=$1^KdK8wIsSq z4F0-&qDSAYqL0#A($hF|B{%QBl3Ws8B{?~&(UTzd&7-%Xk6u|x5ATKn`sjZFy-W4y1##h`X-(e&4ETPbPEGKDuhz%Sl({O-F}~jqAkKDm3O^zlxXN?!G=W3 z!?-u}N40pzj2o(0s^u`m-fvscq-bXo7RInDtWqSY-6ww_j7GKTW07w7u4qgoua>tN zKQad(rW?0n1?6k|g+AU$pA<`#1QWUraBNXUjpCK4Rk9~kE8$Y3h%vPZ#!aY9!K0RB zSX_Rh&cDrH&6xF+aw18vOiigB7?(GpG7Xm;Ws86ObV!mpA(QXYq9ieG{OQa&dtVOc zXpXKw(Y9v241hGBG!WP!qV z)!7IXA4Y*Qn37TTXeCn=kN=H_$b(9QClo+N?SEDjt zjDReCDao&702CNX0(q=US|rjQo?q)9rzn>TfD*$1_~R68a%oXsn6}XIfqg@HNz`fA zsl>wlZIqWpourL?M3)-nB~d47BOlSDmh^`l*h^{KD3R|{qkT9lby_#&?_FxN5KL_) zogP*$HC`r6ji*iNb(a<;CGnkhoLWg;YLu14cV4xx-_q()?07maYO`^F8&CWEaw+7L z3IkkfV;z(_bMDKj1nre``R%l8blrHrEz$W*{d)L9=?V0>DZB3?Sb4da6Q|6+OKYV? zcu%`dbqOvtp7`_e8~yS=Wf`!6d0zqZsUZiq&;#8BgZpma?sHUjiqsE(YEBD&g=G7Tv{CBPv4yj!#1IA8u!Q1 zKqTt)+XD`OL~)DXRz;Z%H~`Ywkg4s7QSXY&IZ6w29;g%NbRsS_N()n$4#0%68ZH0| z3nGG8<5J_X-yIHS^Qi03rNu*MTDx>kp!XvZ zQ|T%YW1-ZEZ@P3{YGW*jI#fepLEVIs-YxsE5YPVLpP-ygNuTK!ziG2Qz|BMX;EcqGIPMb;zd|Wks2Hx`Ri-{DzXIjg*AFJhCC$E}++3vR=tL1wGsQGKz z`C?f5F=oDLEp5TKZ(F1oT~&~=>@0Fhsm`YK1L9KkRsItMcWm3^yP^;UtB4g!J<5}~n1Lyj#T=^M+snl}k-#%Zc zXiCcBCFHj7Yx1X3lpQf1_r@Ns{Gj?&%Ca-9-FH7Jz*)L1=lsioI_-NXxa{UqrY2OF zsMEgt*$__7HGVtqyYF{M^|b8!Q}H-l`A$ANHTk!nlsc{3kD0MkW2wO|t^9}?`y@bn z+Ql8$^8J+`x0=cv8(FndXTJSCGm*k4qBeZ{o|=?J=U;{v`IR55o62k$q2;GRX!>}i zFU+OhypSJ;g6+iVdl_J~BHW&Nc z>}mT+h^dTOX+q@ByRQZue3`eBQNm=Kk`v!=lbI`kaol{>KAja=Zcpr_J~a}*z7`3v zm7h(SO7&?$orKn}u$an#luV5$NS525pN)Lu)u7^O-ZD){|x7{NRMdSL?T-~(t z6M|Dyx*67!-S;8$W5RF`Cw9PXEp)E&@Nc>bsv0^! zGM+CqGPES)oepgn|9aG6^9QP}r6@`jb)1m<+%FcK>ZExk56O{S!bX0-M;s;P&JwGx zPeNx+uS{{O^W~M8^(~9nB($LPiQ|(Y{c5*pKc zzswVw8pP@VPUtb7ewpi|i(ii3WBO%{-N@C-RhJ%h4E-c(V_&EV!KIA>pG2$LY+`Pq z4Zgqf=zb=MI%XHXO4p~4#wpPY3+d28FG-)ekTs#E&mW7{P*rSb?C2vzC${|kd~}|o zaD!($_tW*ch}uoav2jjL3MRu(XX3PSa%((d5E>>fg@$}Nj~axAsS7ouyTBs`sma8d zZ>~M>?l(n;%#OuhPHGm$we*QWSn@BYvmz(N`(=<7Cbo(~e!oWxQj%!Xipk&iXu&Ea z+O*(MO59yAz1Yoe1)lSIU9%#g$s}I2W|GhD@GarVM4yZU=oUF&9zr2yilBy#+&+)G z5VO)Ukn84A7jo9J453Y+zxRc(C4{@{DUA!BP@Ry+MIm#7kxnpl8uU3Rq)w=HLenI4 z8}z9Qxf5#YKFqd}UL6ki5-=?#K<7J6Ld!z;_a$IjQZLi#yV@#4J4))6Qd0oH077YcO{=-jo!w+ACD5XD>XXIgPkC@7(&gBdYOCA2b)RQw%EO~ zI{TCGb)5d%3%RrJ?Fqe^ovoKT&+yaF^i@9g9d+4ZE<^WcC1#r_Y4-9P_`fP)w@PZ4kGmKy86N8{+;&eNNKCV77Sg1^#j7#X@>Jx)$ zV&c+)2!Y-{HHZjP+sl+LdGDyeFeTb_Qemb+_je`An@%eHZrZLesHRT~PI~&?v|FL& zuulu_R-#RYI1G{O(}FQdw8@Cl74Pho#D?v9lL@6O-lHvKOD=#hsbq>(PC}nvpY=k* z62$qcfujlC5*`zUmO#zfIvqIO5*~FSXi9CKHx%S{ASBM5_Fo{9)KHMy zqb?+_Fr<`|@Q4esIbM%cvsRc>C-gOwICJ7X0@PO3#VBM5G4dI@@%EX-oC`i`8|(5> zh*9LDHnlEBAxwyoB+4&}nn@hHxt1gpereQ9;@Jg8!~S}Uk*Ib7Qkh{FqOfUkKLSKL zT0b2>(;4FIsjz7Sh_s`AzI3LO#2XN4L;a-bOoxd#Akaj+dr=rVSO{8b7osVmVLQ=a zyAVwY6`NDpZ5OF9eQ`Yr$(nBuNQaBty%nG&CVq}`rW3}SAW2aCROL*^jJH8bo9?cu zX;~Yl-32M!zPQ7+g?BMP>h87&_V4Kn0~c|ZNvxggLC1abcHHeB!~@fr=SyxN*98XS(|)OKTZTcdT?Y&!@rhQA+R~4f>DOKj(=^d2JtCKWzzk#Y>9^GyF8$z* ze&yBtHVq-(>sxT?hK}YKjpyGE8Hn>`t6vaZy3wCG#^agXIvYzjXfu~72T4+_m$7uC zMspnxt_zREYU7q}z-O+L!7XtT6*t~9m*EA8ZK~c$A6#9!p_)0RRZ_U(DP-1YVu1!L zmTr(}u2a03=R~W4hGqChbWghHlvJsI7sDJ&H_S7~@O%30WQnNGqi+J|7=6#Qs2y&A zh(8Ka^PbK*e8IT8Zc0hiwCR}L;0%8SqNc^5-ZD(s>;E=*^t|q%S&x*()mw+$b#L3? z%1Y#X>m_n8kj~$1Fx*#q9li|QXC54+^y~bhNx$v|75OV3=GP0eK#sYya*W0EZx?2P z9CD8sUV@o8i3`=4c*KRM#a~YHLQc3xT*%tpX}h^R=eXLEt{9KJ5Vu5*nnE{?2LU3p zKNOWCOY;9cT9B7Sn>5_@kodch>9iAV-g1B0ASsD9@3^~O44X4;@v>V|F^scf44VgP zDwBAj*;d>-52GqmK>>MV9vX;_MT0rXOLFo(43Hp;LFJ+3H7CnKy<#2=ND{+rcSL(d zzkNyX1P=f#7XuX9OYZ}ZxDdiZd#ORsBQB&c5+_ljN*|B75X1BJm4?~U7r$ut)NE0m zkB3kQW1JhA>M@oyRL!Bru=?pzgld<*_+V5$(I)jHXWsi=5Sv7s_KbXaj}~Ml(I)jH zSKgxqF-f$^WhPJFqXj8p+GOqYpnPMg!>rjCGUV?!mQp|+yf}t{c;Ct`+Tm}dBy-1< zwu5L%-n_GLjH_qjeA4c)5$pFS-&^J4-S zH&5hrVkdP#W`0lrL*`5m;e?qV5s)(QwCpnt_6~Dx57+eYnfY-6jFg8Tx1Us*`GElp zk^{9`xtSjs!1y>*U+9B=SUpP$0S>H>OIc`FEahcrGkVJV0$%pG>O971#qL8 zfk1mswEhl)Ys9o+2hs5OPcjU6CvqddB%u!L81xSG+(eSa*MEIs&^r?+>GhvX81!ah zG}+&a*y2fT27*ae2H1D*PT3-_tCz-V=# z(-+#p4JIvQ#6+}ayf2!NShx?I1%|l8?`ckUL8*$q?k+IA9q6h-(SAC#Ud0Dv;j0F`iq-Ro~Q+Zjr(TfEJsso+M%gT*hET~k${$7$7UvDuq&A<12 z_`1(`#L8D*3{7X^bdtUXW1V;=PRHr1F&2qu;&i6IE>k&Z-m5Jvcj4Zr3mIM!Z2;5h zN*C@`x{$#YnNAnEaBtCtjI79Xy1s>bhb}Mx{mgu-%r1P}Aww?m@2Si#eA|J6=kR+f zvkTvLV8}Vpsmv~X+krLOflh7Q!nYkV3?r>C*|>#oJ7f?>qO&@(@C^v%l#H-N;v_k~ z6~T&ZUTGvw;^UhVEXK~nNrrrTq8~_Pm-+nihinqo?xSIWW!QmESGsmz3k!@E2RdEk znqKdYea1*}pwsoO-K^RIORoc+>d4wns4ZkrMv#R-r#iBBb7>0->Lf7;bgCn3H;uNS zIF6-L9a+2YrUizC!|$n%tlg*50^`6;mvm(9K9d$01rBuT2dwE4@0=e7fCF8U7x!(n zz}Rn~OY-8|a0=C!{TwHG@ohLpe8cZ0dGT#HMtTEXk{92GV}zIKtj$^bhFqp=Bq6mn zXYE^a4DjY(PTT97bPVuj;=IS+Q+t6i+Q$=oS$W~cN)|F$BP=)2sSGXL5Xl0gx`9p? zx$tcq#&H9ku5aPnI1JqeI+dY?Z{sjp8|YMq7QT(cNNu1~o3rq39L8w_o!Xp*Z{sjZ z8|c*LEPNY>@mZ!zHfQ16I1J1(oz;8n~9U;_|^^svza)Fk8koYB%6to4Ec5s zW2%?u9o);l??dZJb}!KBO4q*cgCW>Jr;A+sZV$#@1D&pK?Rz{Ja}9K=BWvH;!Dwrs zQym$ig94dAr#iCs{Ufs8c0W}|*8Y2o47a6I9a;NNEwb5$F6qeHe`R45me-eh@z(wW z3*)X#XLW1sKd?v?4YS4l<3~)ez&QVM5<5R^LPFX2gzwYvpr{q0&I7 zy0!9|#VBc@ON(@~*BntxiwtyWeQws8BWji|>6V+V=7^f5OS5^^@&_OIC ztS{-7TZzCCHT!$2TmCsLvFMk7FYA_@3SVNZG|(lV7qj48tYWg@ON^BUI+Yjt7_#Fl zFELgc=(4UX&gNBv_YJI)po4if+B8{Gjz#ztle@VZjrNe$zO46h?XW+O6waZd2y?UmL^up zbV**^x}l|sl`>tD7q?t!X=0^Jm*mB5CE`{%yDv#z+%_U^gR^u=UflK}Zhy0MNnYI6 zA#Q!MbV**^Iw)>=GjtPeu^OID-EuP|DH}j*`rmo%EDeiS^5GV0*!-3rE z)*wzY^9k^azx#^1u*a#=u}5IqVCawECf1TBuCU8I*gSDI$htww^bM` z4Rop_98vdv4{S%EQyp3OwhCjVflhT~;oB;Vl?FQ1k%e!oFjgAqR7V!Rt-@F-(ip@HigoRTS2v!?&05J0 z9qFqN*|&2C6)iD-8t8P_*7TxswjAT9flfDU?G7m7)+x)U?$z2IPPD}MY4|;rtF=3r zXo>ODK&N(S?G7bcV*E7FsU2Fo1E-c4KMizhht}>Coh8Ok1D)ETwL6TAd#cQSCOfot zr-3aoYRbQt-i2#-5ZDp}rhzWWi#r31`?oBgNnYIXUrUU!hTlu_;!cEGVvIG=C3$h@ zKrJ!G8t9U|xD%k37-J1|NnZST2gX}*?Z-PX#u|Pv$%`BBSYeDc&?R|s;~m_GW%*Qj@$Vn*zp`{$UOc~w`>qU~y|>nG zu!I}s8pLUb+=vOcT{Vc)Ho0LFZo6s_>t3zh@CSEJCE^R^QOoYb@o3;|!G}fq3A7&) zhP&?=L^T*v_dFi?TaR&69`N27dO!Y5*Guq_i=g}p1F9LIJ8j+p0wKsiT>2lD5}bh1 z;y-`+=b!(BrbKgGs>>2cL`LF*ENJ2gW{eQR^-wJe#KIyJHSrYvHKi&qAW9k3;{)U6&e-Sx7uNIAEn({OF}K z3kf9$2dq{D=lLw!Fb@S44h~qc2F@$wHs_(n!hvdvGYer5A%@b^pNEPI2dXR20qS~u z$ZqaD6ks@5J;{c4#CdSF9E?I5dmp*T5?$&SR?PA~3&Iar4GaVW(vO%8EUN{mkJt^2 zEF;9rZcpXJH0J)IX4zGD3^dm;GrArW2F+k?HZbs9tGc!+xsq0-t+483JDd2AFz8ELuWY< zq#v<+7*+?UkJvqoup>m!X1-ZK@kSxZmUp@InK0_!!Ab%?3W+xdtB^|BrKrWn!|t30 zE0dqhIYS4k5K03TdTPxYIcVg76;f%zj++zBbJK??tm2**)B8s<;hx&LUg?khG~HP=8KueXzBPl~b=6;7IRcAK< zgh>OQm=$JDHRfgw!Jrr=nBMFaddC>N^3KAfLS`zXmGcCAFqF^4S{S;l-ocW*10MrL zGGUaXe8?~GQ9y>CAI|h)(AQA9%AV(mrfvnFGiu9Hf(~@q3@9*W9{_YBpr|nkl!vjq zRsmBJ06M+BZXb46`e<;ARo+7v6%!YLI#bjpue_%|D!7z@IuFxF+MM#|Q+-SjXTV%U z%od+?Ee&D9ZO)hvbmiXDr~7U+nn-S5d-gWVTiZ#@lT?7wvK4@4pjrbEq}%`=uBl?F zKs2>l4MecKHDZ%TleTLh7CI!JULG}5Flb7;8gO8cBZKTXit;ps+-8^?cwmaZxwmBZRCi<&3(=u3P+ou7AG8gkxd(PVoXh=r~Q;@89JA>M~gRNL>^KrIvoQN_z>L)k+^ZOX`5 zowR+HjrA8ARKQMf3}S)o-<503`!yJ*0BXv06~Lx6x@Or7rm1&gMnmJdFo(w`HZ<0D za5m5j4g31MyKL!GG#nluJG)_O0UNM|o*^vx5527z&^7G&;60`#-{LIaKBu<(vB7Df zX@JQ-Xj^*S*2p!siWF={$d>7{U6Tzy3l#%CLwQzHZ{3zkE{HN(TR}GY91_<;!vK_q zaDVIUr@2>(2QL>=_4tgCO+E)C6#G_+L;AY8kfmvFsFBc$(L$ME?Sk3jG)8(HTZk<$ z%4+cJ`v*S`K@#s3$K_kqt^#VC(}8=y3>M;h;PAG)Z|@}4QBd47t$J>wcwFpdxC@SQ z^GGNhOa83C)IxW{mpyuH4cK`zlSpjP?w;M_hV>oWn!H-8G4HBJEB3qt=)VBhm@{QS zo`Ai*(5I0$_q*2gdw2fE8q=o+jqO-oZI#<%vdX(U)>1>umiKJ5Ht+VN8GqB#Hyk=S(P7oEJILhoYS^(*T2TRRCM+1>aN$ zJ9=jHQ@LxVQHWQW;ac|1DnkPfyqdt>`c|A~Ug&_HnwBEsY&?>Mwl>YEL1WWEbeS!} z$}!;LFpUm0pg?K^)U>}sb7wc8Kw<;bj3?^1Ma+nP0}33K05#9^gNyZ<`?-Nsh*E_p;59r^)G9~vVJ3TA<~jtvZ{L6U?XUl$q0@MQHgG{& zEp90t7Ir||u%#)%7IWKM+!m5oswy@+1=#qR9iB}ODq%S-B(Q=6VFpM@0=4+$_#hb0 z#=e4JGb}qzT^0xA5xcDz3p_@9#g6RC2a=#`{KPpvI!IG{w> zf);?WCb-2ZJt*Pn(O5Rz5&+mgY1^_>17gz}1D4aQn@kS8wJrhQkwA|c9h>f zzaC!j)so=2Y=X9>TROAG#PONRnEQ$I%%d;=*b4`UjjVY>958j*B?pMDlzD<2uyxo# z2av6Nd4en)hEfGEXUverZES2*!W^)A*mu{&1gnxT2Miwe(OeQx$o16}46ZQ}mpmppr zvpS$q*kRa4ZSp#xQ`mo4qEhIhBdP;Rh0Tdg&?ct?T7{j93fhRprTxU4j8p2RHNlQX z#%rX*GS)!e4!ax?tF_Ldm?L&RGFBa=&1|_UGZ|q58K9b6fuhvz2SLG8AV6^y2foZR zQ?7Op5s`@~Dt}0)!WG1sF?$gU6eW)V44;05JeWB7096PtMWA07?5qZ2NBm6P$cz zvczmIeBhKZ2^=ujX+~;o zlI;#|AQh?*d1{G;?UZdG6`By7pFdtVO-I_VQJRb}Ys(9aX7RM$Z#U2cy<9k2uyZq2-5_l`cEQtll@exza0(!MvNvP77*af!#)|req9A zJn8TKFn_^DeN%iW*2@x-(MiFzxuF22vsak*AiL+kr+6B(T)XQ{RaB1*nq| zHm7E!=IRD4J?2?6Qgd|!LXSCCMv5g_?ZX=oc}%bZRBL@@9K|}Xv{Qw+3s)v8(GW6h z*=HP`sE|80UN-iW;09`NCp;x0?HU4>CSsxk7`%`U!GW0$aAZ`^n>*srhT9W^1D-!- zRxMr~Y)_c*1gIZO#3Q)dO-vMIT{qmDO-ztu#^jj8-Qo3)>( zuA3~QGX>inSUY^#-U43hFv;y5z{9kw#Y5AS_?ryk6?&1^8{40vW0mkY80#blC*0;} z)HdVA2I&HiW5qsRp%WP|Hc1zF+z{pC75WhHYz{B!f*sIS8+J_Drn9JOKQbIBLrYcZ z@~*e;ay_-Iv7P1CU=7?DW{sro^h`DFJ!@xYyfyF&wMclu+BG!R4-S!>6nc>WUpIeh zz?FqA<#ml&SzhmvDtI?ADlp(IJWkc+EyTi&lK4+Weszsmdm<+{J88FJO@Ol6C__af(4 zDimf{E5x2gQBl_rY$5)_Xy>lB6Z+~c#FT9kH?Ejz-`4!P?PvQZBh1|va4oeO3b0`= zw}5Nu7L8^b+Ps!d#O1bB%+BL$>|UW5=lj!k+q!Iq!YP=~<%K3&zCRo=Tugz{v3Tq7 zngH9zUsw74?1I+1!NIdq&tH&-JEZBA3Coe>(0D=beo0u-=Fr z{M^6---fZuAg5~UP-q6zwv59y2k}YQFeO8Q8O+xPu)2vU+03D^4CZMOtEEjtff>xs zGFHcD8;Z(cI#ywLs?+9j+1x{sL|)^7(ZEzJ1HN9*SaOtc23}T}ePx`(-5+i**VoN? z(^5%0le!iyOuH)D&|A&Ut8QR|?N-dw;SMTp@?znY1^WH?%U}Ny6dY5n3Uzrt9lkaM zb|_MV3D$%~maGNkW*H|VOsg_f-#Hr!reiKOBempyDCmP()Qr@+K!&0|m_yA-t(#*g zw2v87MvAEdTdJC5fjl{=vMWoTCS%N@X0Y=W<|t$={|UuiKj{e-u&N}g+El;umbfZ} zJ;d5%K%%ws_LHBL+Hjwb<*BO9OUv`SVu>kuyoTjPsFqlf|K|BRV@#qlRJzAZXLjby zf7XmKh01Vz1-)&z5BruHlG_Fn(XbC=OrSE{*X`zbc%wqGbJ>EQIUEWljPcuGk2ju8 zYeLPRK8HC!FvP&t!A-`P-HZUCf~TsHAU{XNtZ;}cM3C18VmjClAcEXB5R={b03yh5 zM!e$=S#sMC*xwAG=GLs14RYLuY^~f}pcN#%Jll}1r5kfnL4v#8hD^58EF6YQG$M>+ z9KoAmZ;!N<UTq=8R!okxt!i`osOd6ZZ0MkTh%bmo0 zYYUHssj+P1L{ylS>WtRPBX?tjKzAFLgM>-VTkFR>tniE24OUIIpklSspv-D#J8i9E z1!#o*nZkO<>+$%l^_>bhBDO`dR7YE38fmwf!^-X=!#nelTI-QpL~iT6i)_TckK9&$ z57{J2Z|9eog};TzHl)z02gy!cD0kJQNei4{J%cPUjB7AWjx@mV^G9apFtuwNOyc8D7$E=&wFTSNiYl?>*5lNWE1jc9QF#YA--GmJX8h!dkB+}k*z*M zt|U`XD!q{+(CPY?M}on+Tt5Soj~S-5->z#x?|_1vsu#$f9y=5~_;>V~RGJ+MJI!G- zHa(-k@D;9^?zihlV7@UfuL|&|u9PI(z9goIT`1c!&Cb@7S3|UGNHgJ|e zW1^kG5-hTc*VKoAb*kE-f?7u!uZbqK44xC5NHczb{a$QOacc2#7-cp z8_lH$Q;PKYHsGhW^ZRf+M|uu=Br_gOR8I~tU#G;U-HSGhC6xH|@1lUo6qNc#bk_hL zp#jL7z{=`uLM4Y<`DN}z4+4{MhKK1bnj*f@Voz8IauneH_3>^Sn;``#2|CMRgpAjeJsWU&n_PmiX2{E3*u#L=STlYzvIz%nufQGIdOPO~o>&vH(4F&z z(ro0Fx6dRDM>jE<($T=g9doF7-4zc>O`$G4g_lQmcNk;?X+m%%f0(~x{t{k~L2&h2 z`-d4k#-r-3Jq5sX%QYwn)4R3-8zvJNBX+D8ynTT;wO*LL3uuS0JS{PZ-KWoMeDnf! zGSir_%j9}Nukp>>BnMHYyOF0Fd9$1FimdN7%&rO8!1$&CJTUBnmq#u^+flmHsGh(g zR&oV$S|P?wBf~MK5u_4BXj;VrvWsu z<$~r8bQ(aj4mMAka%VaXpn(M!-n_9+17MX>ga-_EQ6vE&OEmxnraN*L!CKNrW@-Se z(ulk5;p=t(C?yxvt8=09nQT8o7A`yJmCjO{zLdBQVhsCIUXLHc9wH|8;(|MfG2ugq z_7PF@X!;j}k?J7Qhr4uIAHmvJK?4@#`EuRF*<@i%Lq2J^7qVMFCZ*?NFSQpZ$`*j> zv=PvEbSTh~kEVc&hhcr30snJ?sj{a#nhZvtUwWZg1l!xTIrL8?Te{eIz4lv9lZk0c zB*aj~NyC-3^n_--U|Dc;%Ru~?sR%)1gRw%5afV!m3BCSuoQaTVa0vy(X_m3vq)Q6W z7G6hreweAE67L(Of#{z&I*U$ElhJ0}QEPXwWsuQm0`hskf#*$XRSJs>E{8lAi)(9w z(^Ui1G-jttZwPstHlB;>HB7K!5COTF1sq<*llT{iLd7AN%m6sMZLX(H#!8E2txTW) z;eD&1z+ej0e)sg8o=0#yW2UBg1Yj-|ieE@Ksy3#*OOSN$YOD{-yQ@gw1ShN-KOz=$ZUod!_ zZVYmmih7#ze#G=%KwOgNdZJcu{0;)js=0(}FAnp3hzDLFq7_bSY2Pv37tq%KmRw-2 zkKrbx{(%-Tw2CuurvnuEW4otR4-RX@X0g`Qh`oi3WYb&WLQLn0y@0jh!pvbpBF`j$ zA_&=H`s9OEVgWY!j;2D|oangGE5b`FQ84w#D7&}wmcF`@o>g&i3f7KnB`*_u{s7F* zjHLxCoU!o@-XNO6aII}uDAzt?vtI%#cU_T;nW(l4Wa>h7a>iy+v2tqAA)OBSW9#6n VD~S0%hB4zCsZv>S%zj&5{y(E+Y3Tp} literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/SoundUi.csv b/titles/sao/data/1/SoundUi.csv new file mode 100644 index 0000000000000000000000000000000000000000..0b2aa1482c9a7691887667631a9415eb6889ff6f GIT binary patch literal 14151 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

      ?1DX_>edo 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%0A82>Uonm4hA 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 literal 0 HcmV?d00001 diff --git a/titles/sao/data/SupportLog.csv b/titles/sao/data/1/SupportLog.csv similarity index 95% rename from titles/sao/data/SupportLog.csv rename to titles/sao/data/1/SupportLog.csv index 92295b11221b6dcb7e2ce64fb24a8b4fff8dc783..1c64be57bc4d184da204d543b60145230d15fc86 100644 GIT binary patch delta 2909 zcmYjTcT~_v5`H_gtAdCJ5fK7nmjsofDA>V>ElP~_>?Mk#BqSu{^fa->ZY=oh7?o#< zEgmZjilP{DmM9TTtR(vML{W?-YBXoLZ}Z39{j=})c4ua1zWKh{Uqy!hf+hYO9$sh9 z0Gkl6eY(XIk+EY{q`5c8w#R)8xiv#~>*E}*~dO?WoQSyjv z#~*{)4m*!!$|cyX{Q3XwTK;Kw`6}#AU&ra4G6eOyjjzUc;nDei9NRg+8Mcd_gs|Q9 z>;T#1=O}mMt4Nl!q+(dSDyb-z4j!sI%X}|Ymt|*lRh1>rSH-eH=8=&rg9B7;@vEtb zrB;Ay$&whPo{@A5QPC`)*HgV&N*byPl7djxn58UCHDak2uKKc!ic+mvGNTnP{2@jq zu+(g;Zj!uguiCM+jaLCI{X47XxV)=s!E&gF3S+s`OZl+8=&jyjiSMVn;B=rG#d3GB zxJOZzs8?81)n<~kH1#IS`gC=hw$7WR5N3+H zh3(H(2NpX+eM|D`7wQqo#JQ>-%i;y@d)AjKf+c6Es>yP4x$R_+f2bO=1V2$BEbX4TodM6)Lz3>V)MJv4L|s|t8Hz`RRR|#a zcu_x=r0Ud!Ww|efvlRIe;JsDL?f3<`H9o|xAJn5?Nrp9~8Z2p{1lAOUQ5nhRW(2$r zL=u{%qsa>~V+dFTwxMdsygi}CL*l3zZ(Z;fc(kw!q1%Ps32b`WlPa;idIuq$R3f7x zm5At-safPN(R~SIj2l4hS*8u5V03e+OYF!90tYUPrZ-q9$=P@(yYHA3Tp>xR^aDw& zGzwzrn@;bs?3+Z1EFM$oFGy(`0nDJ8ghn=)?b3^yN9Ra97Se8#ye|pR{j!wKqG8La zfaJrKbc&?I8qzFpWx6s4Zy+>#Y!-p@eYUurSSzL!pp_lr6 zfaHxvTukybly8$<4`U=%DV(p7lt*$37#GduByll38!Bk+VvcCXpOIYYz$nplVz9$I zfx(OTZu}CKZ}WMQfJBCxUVE1tv$W~Qpi29JZYO3i7m>Ue%9$ij0ei{bj@M#pno2Ni(lk3#>OWFJc8w1p)-6{#EIzNF~;DX@jXKaQp~GKdYABF65k)) z<^JaxJqx(R-vO{vMvnuoI^X!~?(%769LbVh&QQgJ3YSRT`>s*{dgy%1AGDK@LqlCh{WXN``?^Nay0%6p zmFntYEMppI#7}Ol5i{dWXDLlJI5#aqzs|C*xkgF<|F4j_7fLv(ciS2u7B5$W)r z4)1VyKG?16{r3Tn#PsT&qy40LwtkJJOO9^CGBeNN@?pNlL-0X?{+%SL&>=g%NMowa zIqDEUbHZ7A7i-Yor$k>Mxqa4IUOT6sl3c#%d@q*jRye(?pO93!?vSN24XsnT?#j}m z!rc;kPlGXO51r+X#~PHb{!D{6J)i5bEPG$MF76}Kl;x%|$i9|`f$vB!(;8i@4r$6& zUjzHvH4RwOH^4x!lY`L5czOJ^ApL6Py_i-4>QPfQZoaLmPeZ3VAI0w zr?oQPK(Ms|!&bF3K>Tn=Q;4o~a^Kq%4CMbyH&d5$d%DjS?-=|X1obi3DYw7D^ISC0 z#mXCOP}P=U224pBX;9n7_YAakI?3QU-@pfmXWhck8x;1`IfG*r9y)Tho^1 M&7s&)(;J7yZle=?cdjv4}IT+!S63 zSgdS>6;|4O3fOG>2FkT|9*~aN+W{^(+<)=8k~k zmT9n@;#Cev@O}vx3B6 zlWM^-Ysw2K*G{AF`z7feq@y$G`%34029pL0>;bJ7b^wf6>Lx+rZBize{$CXA&?NWF{r}yzeISK?@`ys; zcuIcn%R-TKP`LthdgB5Z`u-4#_htD&p`uq=$>AhlaK0}pge1ZxeP9m&8Fy*!GA z0k1~0?!d;UZRtYK&wUAdN3hhA+kWsKQq((1k0l=+K)8^1nT43k6;+QFW|FMOBT zr^6nGCe@z%%;&iuWF=NPlOLh9`7sl&;+2$?_g*^6mQ;qEp85&a8cxp%yQf3aZ-%CTDq}L3L)M@XG ze)pg3rRgL~J8O4Y{8KO42nT(ll3hbKX^e-%yi{^DzxJ@ZwYkB{WyLB>5iSq8^P}T$^0we^QpWS z`6cmBfIVk&ID$ac+p@VkJeJ?(xo$GahgnWkruUk^a>){tNsLy)fT%s+`jZIBC7FV1LRw;D7}c93w#IRPocm z>0H!+ey$Qk#{wJS3i+X}_zCOZ+Xk}?;iE2xPpf;->DKN_SMhQ=Z^^6uo=a&6Qt=$HS6EMF&Sm4Un zb7KV^Yi7Kl?#LZ3vY?M0DI(#&JyG;CSD?k$+SBa5$+RPW!tc{X|6V`nf37Wi6}=NeCk~kM}dWxO`g9M3Sw2!b#W5< z^~Hi%dA)?Wrj$n-AqNsaBp>uaCplBgEc(u5h}C8@dmRqP9ubck2OWCZlC;gX7Yr-$qUxml!J}i$k9E{;Im;8*Q5S*gq#QLm0;}35>1@6vBoZRf{9l?$?VhMTS?b^?dg(6 zd)N1&giTECy3 zjTS%jOkir~4C$oZ(35MiS=2Aq#Gf0lcdgd$~0vT-fB)aal52Rv1F$tF6QOR zV1)m$*OdFgeo2p?dxzv(#4XMT`P|PU;x!&W0MfJWSD~Z620$ z=$;v?s7|-z70n>K5sF@)Z$_#L_`gh4-H=bfI7KsO^h8DHojKXa#Zy%@*1eMyy-0@7 zR6`)=&oSt-K;1^1s$@ks(Zxk79_v0!6`f;?<)$7kKPY+`yi8Tpe!gjn=-Ve<(K+qU zF!it4qz1x2W{aX(ziOMJ8M`Z6(WCuljv`*x*`sKRjM-=K*Mq7q{7R0P^R>%Y9U%8O Lsq(n6{%QReuM>dS diff --git a/titles/sao/data/1/SupportLogType.csv b/titles/sao/data/1/SupportLogType.csv new file mode 100644 index 0000000000000000000000000000000000000000..fda48a13d09c63e2c206e0181af0d0dacceac83c GIT binary patch literal 62 zcmdPbR|qaGD9A4=@ySmQsVqqKOwsX6%uVIxGSpFeI(y#JeJhoCxr~79J=>r5Oarow MLE^if&FKWP0n?Wk>Hq)$ literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/SynchroSkill.csv b/titles/sao/data/1/SynchroSkill.csv new file mode 100644 index 0000000000000000000000000000000000000000..311b6dd0c31a2f70e963538229274c9613727c26 GIT binary patch literal 5631 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 YGZmw-qx_Zx-_S^&u-cG2rHiEqMIc@PyQKTg5 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 literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/TreasureHuntTaskText.csv b/titles/sao/data/1/TreasureHuntTaskText.csv new file mode 100644 index 0000000000000000000000000000000000000000..b737d5d33872d59f8e26669769fecbd75ed9774d GIT binary patch literal 1014 zcmbW0-AV#M7>3V-?jiPgz}Xpa^*D$+-y0Cz1BoK6KD6zZ;{PR zI+?OlnY5`^Tbb-$!d_p~DJ43gL<3m{`2gYu#5KrA5I-QFL3TmZ3wqIOv_Rmpqux3V z5M@neb2DaIoims~D0o^+i>`|9Ha^h&IRy$XoT$aTf*S7>pCG-|P(CcE42v z`2^x+VgPHOzIopl=M{Ez|W5#M}gcIIJ^JM fDB{uIEvjc=T)Cab{a+@0N;!Jw%5yNa?8N>7d#(zi literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/TrialTower.csv b/titles/sao/data/1/TrialTower.csv new file mode 100644 index 0000000000000000000000000000000000000000..ff51b87ef109f607afb799b20f41012a4d58007f GIT binary patch literal 2116 zcmY+FONv}a5Jl$&wFfWYf%5;E8ayy~VyidMl4@XRb%WY%d-*;m-kTUiWZtW&%6l@h zsZXDNdiwF_^UKrQKaU^3emnknygZ)YAOG`y`S<6y*N^w7=Pxgh&mUjt`u*ea{@3TP zkJm?hnQ1d+o!X2_E8D}6Bgg?n#@_qm@|SN)+mLq z&jb(80Pi5#B!5e1@5=C}tom=;W8N23gg3TP&Hcv2v$3Bd?Y4n(bZ zfP^OrqH-fe0#Rb)a1D8(r#MWHlM3Udfg6hs51COr;e<5b$wK&c||FIGwerHY{EN@+eQ z5tOc!21*q{=}Kv!R0-k~H>eIu6+!7r>7Y~*l&+KxN)qbP`B90n1d zD2TzmHU<%%D2TzmHU<%%D2PGmp7cOrBT7FeaeC(7Xkv{riPK3Ro+yaPg>}fo69q9z z*xTiaf|w-i&LK__1vNdILBxkeK}_zeGl=j+K}_zeGl=j+K`a9@i10)~ECPdad9hY z!o(&@&M++|5?Ut2X)=;9Wm+&rCo>6CrUg@&3?)pNmcJ{0XV>&3nJA|_u2z{AOwl?o zQ>Fz|m_9MJOqA2)jfE-Gg2~REG&zY)l+)$6C$voaV%p_0WkQ^t{4_?VO#5Qm?J{LT zoSi(h^CX$}#k6^9@p+jLXDe?lOquq@w0Ue{%7i#Od2M0Jv@a%}n(nS{qD1e1*P@DC literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/UnanalyzedLogGrade.csv b/titles/sao/data/1/UnanalyzedLogGrade.csv new file mode 100644 index 0000000000000000000000000000000000000000..b0bf28ae4f523984fe7fefe670bd9374364ea230 GIT binary patch literal 170 zcmdPbR|w5Z%uCFvtV&Js$xnALN=!-hOwsX6%uUsC&d<$F%`5Rt;pH;aQF=aeuab^Z zV5n=nyK6|ikH5PTFPD*y(#x(^3}IuCa07<02~fCwBC2qFkYkW5&?HkGr57`IqR9n^ K2Dk=66#xMDf-|WA literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/Unit.csv b/titles/sao/data/1/Unit.csv new file mode 100644 index 0000000000000000000000000000000000000000..70fbca14612ec10c5dc675c50aacfa1e1a82d853 GIT binary patch literal 561120 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 F{{t9rHp2h_ literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/UnitCollision.csv b/titles/sao/data/1/UnitCollision.csv new file mode 100644 index 0000000000000000000000000000000000000000..98eb0f993ebc1939385bca4b39648302945e7eec GIT binary patch literal 365911 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 sLskrD>#mfC^c1$ubFVmc+0*?7_qV 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 literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/YuiMedalBonus.csv b/titles/sao/data/1/YuiMedalBonus.csv new file mode 100644 index 0000000000000000000000000000000000000000..66bdb186761d064518a4ceb542b6afd2c4f76ed9 GIT binary patch literal 172 zcmdPbSBNal^i54k%yG)kD=qd+(Fra|EGltHEJ@XI%}W8%yj+F`20BVc28NdU28KYS z1mYMO=o^^n8(1nBSs54rkrGImA&QJSR0bqy3{(!%i=x~JMFzWaV=GfjAOac>02)*z A>;M1& literal 0 HcmV?d00001 diff --git a/titles/sao/data/1/YuiMedalBonusCondition.csv b/titles/sao/data/1/YuiMedalBonusCondition.csv new file mode 100644 index 0000000000000000000000000000000000000000..5f33487069cc9f1747afc5718d135f8940a99b8d GIT binary patch literal 174 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 E4+L>!>;M1& delta 6847 zcmY*d30#y_*ZWd;>Q&@>gr4MkLR6cimC8Ab8MG?)LE zw@uM@t(5Am{Bo_gEESjPYt<~8%WD2|whmWbJ5X5EI^Rq+T^rII8Q_3dhi=fJrtm!u~~ zF2mW#YT~(Zus#n1&FSHW+0a7UCl*V<@vNT(krY9SNG>!gxTQsxb;3c`tKy*iMgh8Ye(Jv1`Oh=E2 zHWA;!PxH9>sbMTjXqa>-;-Z7t8eNO_F(k8E!47Lm-HIDAwZyXXqaLLmq3dR=gJ*l0 zMUB=lFKz{?xNs~z1N9v};ZC#F4Lw5;-Y~OG!*1%g$-a(7iK6cbx0`JSI)2y_{X2P> z1JRwzN!5nrmVRjNdbrJOcfkBp z7EZd zkhq9whcv7#$%CAvSwy>}S!x)+6c;i}(oW=_S7s538@;McW+(IuA+vUg>77<-R%^WI z+mPGU4eH}^$~si{UV+QKm!Ukh8Xu%C!ZOnyLeFztql^1uQ<1n?K zCqj3aT{iw!2XZEnCf$z1=>$v0BJ9lYAvV*8U?zFOts$;cPwdRh3wN2_y4-(Rzq~M~ znLK+zxrJzQoYs#Jb=%PK(L}U$j)Bm!ncdQR$~UwQvtncaHHfvWG0_A+yq=8$i-&3G zKA@fgL*t{nlg60=v{xd|D1!Ft7UY;7%#Cvc^U*ykfbCfUsq;ZRjXpC#8n9Vg45s_S zN!cYG5o>rNtVFrxVT(1qB+@nMlk-NNN!M}6C`XD_1f|XwC$au`#^#A|5T)!8r)-}s zkJ(Xg6Va@Tv!{$9#c-Ni=OxLstrmxuaJ_7Az+y)uesu(BB@xcmJuxyEgG0V_XoE8F zgVQerR$Ow&V}omoAZ(J*e0HHhYZwStCt#n?CP#eira&ZK%9@^dVzJZ_gIsb@+7ihS z`Dm*z0ovMFv@DS;k&ac9%lE356A6ac&sZ_5APle?NgyXXfFs#8g8BD&kXjB*Wktxb zN1}1a?cFKE{J->~Tb-8PpEd2pTNAn?Z|EfK99oO6IkmVa$B*{8!d9EX-|9md;{twqf+Kdug|6+#C;*N^p;LP|&%h6gY?FMtp7#7MiwTQg{6lsBL7Z}qH2iFYw! zVVCiC*(;oBR{=LIpCW<;3llRjE#DKN5urkF#|Y`>RU^uUwhF&1-aN`oy8W*J6dZE8 z<5ma~gVxL)r84-fC?q53c=j)7!2SYCD>UG0N;bYP5GVNMLa}Lw6Q_`lX`@GB@92f- zQB;eqMFLu1`31a%XSU-$^-McVY9^47uWq>pKUxRTIYVhU+xrQ6_?n$M)NUT8#BeAHEC--3O5S$o`w)qk0H8C0k zCJGqb27VY*gawn*m=p1ngw2#PCf(f^M<z2s?3f94#4hi{l`Ufr}4V;4MlGaeL#~)H)04cwUy4)o<<73dVfx&2 zmgBiOOOaWz0QXibN6g&iSU9mr7%i2 zB+nrOJ5eZgABY>ZIgxguYt!+T zqL~fnmerxKwoWP9Fm#Siin+*L9vbVwz=}S^8X9F431720@nD6}8ET@UV#kW*YNQ*R z=Gsu~&y}QIL;Fe!8=+)JOx*(1)m7rlx;ZFZISVZ-m!of>QtsQ1Az^7a6evXdRSU3e z)jV8UH4C2lMR=iJxyxY=K*}u2{h|QT^7udLz7*HjE5KcvQhDGX zYKTi$bl0X3Z76zM(|EZ7sukoCQ5z;=#s&qw%dU9Wh&S%;YSOdQ8K;~=Md;mmiZm!j zlc7D?4)uJni3+7%8-3`w$)}`tJU!Nev%j`O*?ogeb~k?8G!NzXR4J(oM+z3m@xaV^`4OCdpAEA)wf zaZOYJZrJL>?^~--xy^@$cq_6lf;W)eI z(5YI?!u7|7E7G8P*dTHk<-jA4S12ZxJRf#O&J%f9_e20;PX@61Nk9IeuXaxWKkaG2 zioHQx+glqc(yUu^^X5^M(w^DlX|Fpai;A@5f}1RvjNMcqoyioer$^xDr&p<=wRP)nAN)*y9&v^)oRnIpe>YokR z_|G7sUyvGB%0ql$DHS4xFL-HS?2AEcc`<;Pmx9>*k{{*+5)Z3J#wV1qbnHKnk6#XK z60;N2P!F&p|DBI^FZ;3j5)mINN95roz*J`9rmLEBR79c#A&pv=hgep4M-j?klwD ztWWt!8Df=&2%ZynoL7d3Iz4oxg}{#f7nC8^zZr6xwroYukCh?TkRb~$3Pbu{Y{K&w zHzMbg2E6u(A45NF!ii5CF#1ver!ECi_*noaKien~w$mBjHJ>Vwz$bYog7Ez3K}`7~ zfRDZiqV&rkE_~U5>0hnImtU<8=~JnA%7TZ!%8#-MtL(a)?=D~&DF4P2WtAL}Thr6A z@)J7G22Ovi@UYYDixSfj_qCTQh^M}(Mz3$xDYet?lUw?V0R+SKZxsx7196XvLmDjw zc3k;R!C>c~#;M4TpG_8?pYX zI+G4Mv4Y>LtrAUbeuV!Jz?vUwagF{j|91dC|GNSH|7=3kkIS+8$JGe?iLxVoH-2hD z)6b2F`(-nB{SrjVuK_gw8btDMLG1pm5t-K-vHw~S+V6fm^}E`dgI;g@-?QNGRrzXt z<_{J64y&fH+jCubbH|_RKso5h<|fc(-grYkW$LcipzV6KvW6<2xK8m{cY`A0#xfb4 zRKpNbd~*_u!I7)c!L6Ihxk?@~0S6smnoC+UEW){*8%Iyz;#!*2wiR z*2E8nvH3hBoGs+r!>MPApk|?HK8j!gKCvD1^LN^@Aipz`)$-Oz;*E+T-s@2`c1(LB zPm89&OuXY}rjT}0{%%dn;(;he<~#YTx6x`NJJ2jAJKR*EBq&Z1KN!x)W-C7&%@i0; z!z#hxkH-p$&L50nH9RhsDLkBpL*T(5i)CK^eJoRmI1TypkU)g5iedx_KOYyugsLv_ zvkO5YH^glGRL2l1P6|DNO8ZU{m6NyAK1({0W#>DQwc|Ro2L66$vTSS@+WR|Qs8`gL z1joCQ$$8y~Jlc(|=lR`9`le_`_Mi#Q^`Kr!JT-5}lU{MpkYO%Dg~EjY(N&;g@IeVo z1&WK_#S|#~Tms9NK6e=!U+g1r{_IMoxcSCJrUJ)BAw_{Br(jM#KPhB~i)sq-n+(N{ zlG%A_uaNol{0se{5u@g_@`7Zhj;YIV$q3>Pr7#L1J}HH4|2%~ZE9*^LzaW~)sYJe; zO1*Ju)H{<#y}~}!ob1EoskYIzxNVq)Z%UOQ9!qBp9Vjgj+pzThJh%bO*@e%}VAJ^P z88ksoCSmkOCc!hJFM)TiFHKd{j|3?ifgO9vVoK*s^H4{aM5tpb`0)MuK#mc)NAt^)gtE2qT+jrHeL0 z>^oH55XYg;-YOgOM2YH#SWfAjW-H6TIjUo3r|mXY%BR?r2W_M#{UW9kuFbYHD$2xY zoBRb$_(jPyp5;(ZBbjho8ZWM9k^G)i^0bThal&K+;DiP_Or0)O+ME>ZMGirgncFZ9gx>rzx3gUH~OT2A!LkHhV zzvRaL*pKH|vV6X|lH}3zNPf>e>UEe;C-VOJ1XAJxG2;R;W0jauG)W6@PC@BY-Yw@A zGOx7OO36f|-hv7hpBopkxe_tT|EsTOa#B<^o6X91#jws4-CcNL1%cq^7kw)E+N^Z* zh~z6Kq2zmbaR>yfxO*&0-Ois`a+6JuYj%@wOKucF5C!)>%UjepKL_vviIvYlFOUEk1c>AFDj$?LU6YEb=-S%u-@iWJ zetvv@KVLq4!rQ~ikLUHz`%A}Y4KD-hTqf*X7UEnsdOKG`FUZl03iQGfy|~`aCImZo zE3yxaLi@xhwl5698&j}h3U*AvfhjmK3m)y<7k3^VtKh*qEqF|1@Bftxb^N_6}|^o#qY^gePJ)I^bICd!_A1QxS3HMH$$rA zW=gd*I=C?%Mu%z|9m;8RsHf4Pphky^8XZb%bf~G(p{PcOsyZFKWjdS=b#*!v*6C1L zr$cF-4z+bU6xZocU8h5NoeuR4I`}f^2s%_a=uqOILydzDMGiVtIp|R4phKO54uuXn zR66NU>ZC)hlMcmBI#fI9Q0}Bdy^{_FPdZdQ=}_{dL(N%_G0z{5SEJ^=K(sO_kQ4^zaRbrs$1IY diff --git a/titles/sao/data/GashaMedalShopItems.csv b/titles/sao/data/GashaMedalShopItems.csv deleted file mode 100644 index c5a46aef3f4ad02b57b160fee878347396bab3f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46917 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 diff --git a/titles/sao/data/HeroLogLevel.csv b/titles/sao/data/HeroLogLevel.csv deleted file mode 100644 index 36a53b93b403e4a0a4c2f3753bb6618955363802..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1249 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 E4NDV+VgLXD diff --git a/titles/sao/data/RareDropTable.csv b/titles/sao/data/RareDropTable.csv deleted file mode 100644 index cb674f6afc5e3cedafda1d20a0d2dbd9a38d3bbb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11849 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 AxBvhE diff --git a/titles/sao/data/ResEarnCampaignShopItems.csv b/titles/sao/data/ResEarnCampaignShopItems.csv deleted file mode 100644 index 6d5dd17081410e8e3fd8905604d50d3ef5d88f29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 630 zcmaiv&2GXl5QOi2mObpQ6G+ae2QH`zsCP@Tpi-SgZV=+_*|iBjZUhI9Jhs1`apV3z zZRl3k5B6u@Z4dph9-Uve(9GPlu07@3lC5bK!3SjYq@Z;{VoRV=7l0h5N|OqdqFmX(4PYC%gAeY<|{&JyL(`os&a;@V(9#3 z0c%7k6IE68>S!~u%~PWBBg@^ZR-vg0#b*7ih8yHq(G)5i5xSui z4MnFync_M<#FPj*z2aYY#8euIf`|}b%&Z7|;bm3Q|F_IO)*0%roOw~(ahlpIpP zEMVW@d<7A%f$=ya-dPHAR3ft;8dyEgzL`zhJc`$P@K_tHKCrjI9fRYBR`UOtl-wL$ z2CYWNvH9p_?mqh1(BYnsZGRz2PNt$$>(N=g-+my3KSMzANxlQS0O#2c$Ofx)sl90E EA299|^8f$< diff --git a/titles/sao/data/RewardTable.csv b/titles/sao/data/RewardTable.csv deleted file mode 100644 index 929ff82bf14bf419260324eb101c33dfe7034b7b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 323785 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

      Sword Art Online Arcade

      +{% if profile is defined and profile is not none and profile.id > 0 %} + + + +

      Profile for {{ profile.nick_name }} 

      +{% include "core/templates/widgets/err_banner.jinja" %} +{% include "core/templates/widgets/succ_banner.jinja" %} + +

      Register Hero Card

      +{% if all_heros is defined and all_heros|length > 0%} +If you have cards printed on the official network, they must be registered here in order to work with the game.
      +Only hero cards are supported at this time. If a card was registered incorrectly, contact a sysadmin.
      +
      + +

      + +{% else %} +Card registration is not set up on this server. Please contact a sysadmin. +{% endif %} +{% elif sesh is defined and sesh is not none and sesh.user_id > 0 %} + +No profile information found for this account. + + +{% else %} +Login to view profile information. +{% endif %} +{% endblock content %} \ No newline at end of file From 7206af83cb2d794cf8b22671c81a01b2780d81c4 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Wed, 26 Jun 2024 22:30:12 -0400 Subject: [PATCH 63/76] sao: fix upgrade script --- .../versions/a616fd164e40_sao_backport.py | 759 ++++++++++-------- 1 file changed, 418 insertions(+), 341 deletions(-) diff --git a/core/data/alembic/versions/a616fd164e40_sao_backport.py b/core/data/alembic/versions/a616fd164e40_sao_backport.py index c4b885b..93b5993 100644 --- a/core/data/alembic/versions/a616fd164e40_sao_backport.py +++ b/core/data/alembic/versions/a616fd164e40_sao_backport.py @@ -18,29 +18,81 @@ depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.drop_index('sao_static_quest_uk', table_name='sao_static_quest') - op.drop_column('sao_static_quest', 'questSceneId') - op.drop_column('sao_static_quest', 'name') - op.drop_column('sao_static_quest', 'sortNo') - op.drop_column('sao_static_quest', 'id') - op.drop_column('sao_static_quest', 'enabled') - op.add_column('sao_static_quest', sa.Column('QuestSceneId', sa.BIGINT(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('SortNo', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('Tutorial', sa.BOOLEAN(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('ColRate', sa.DECIMAL(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('LimitDefault', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('LimitResurrection', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('RewardTableSubId', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('PlayerTraceTableSubId', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('SuccessPlayerExp', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('FailedPlayerExp', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('PairExpRate', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('TrioExpRate', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('SingleRewardVp', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('PairRewardVp', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('TrioRewardVp', sa.INTEGER(), nullable=False)) - op.create_primary_key(None, "sao_static_quest", ['QuestSceneId']) - op.drop_column('sao_static_quest', 'version') + op.create_table('sao_static_property', + sa.Column('PropertyId', sa.BIGINT(), nullable=False), + sa.Column('PropertyTargetType', sa.INTEGER(), nullable=False), + sa.Column('PropertyName', sa.VARCHAR(length=255), nullable=False), + sa.Column('PropertyName_en', sa.VARCHAR(length=255), nullable=True), + sa.Column('PropertyNameFormat', sa.VARCHAR(length=255), nullable=False), + sa.Column('PropertyNameFormat_en', sa.VARCHAR(length=255), nullable=True), + sa.Column('PropertyTypeId', sa.INTEGER(), nullable=False), + sa.Column('Value1Min', sa.INTEGER(), nullable=False), + sa.Column('Value1Max', sa.INTEGER(), nullable=False), + sa.Column('Value2Min', sa.INTEGER(), nullable=False), + sa.Column('Value2Max', sa.INTEGER(), nullable=False), + sa.PrimaryKeyConstraint('PropertyId'), + mysql_charset='utf8mb4' + ) + op.create_table('sao_static_reward', + sa.Column('RewardTableId', sa.BIGINT(), nullable=False), + sa.Column('RewardTableSubId', sa.INTEGER(), nullable=False), + sa.Column('UnanalyzedLogGradeId', sa.INTEGER(), nullable=False), + sa.Column('CommonRewardType', sa.INTEGER(), nullable=False), + sa.Column('CommonRewardId', sa.INTEGER(), nullable=False), + sa.Column('CommonRewardNum', sa.INTEGER(), nullable=False), + sa.Column('StrengthMin', sa.INTEGER(), nullable=False), + sa.Column('StrengthMax', sa.INTEGER(), nullable=False), + sa.Column('PropertyTableSubId', sa.INTEGER(), nullable=False), + sa.Column('QuestInfoDisplayFlag', sa.BOOLEAN(), nullable=False), + sa.Column('Rate', sa.INTEGER(), nullable=False), + sa.PrimaryKeyConstraint('RewardTableId'), + mysql_charset='utf8mb4' + ) + op.create_table('sao_static_skill', + sa.Column('SkillId', sa.BIGINT(), nullable=False), + sa.Column('WeaponTypeId', sa.INTEGER(), nullable=False), + sa.Column('Name', sa.VARCHAR(length=255), nullable=False), + sa.Column('Name_en', sa.VARCHAR(length=255), nullable=True), + sa.Column('Attack', sa.BOOLEAN(), nullable=False), + sa.Column('Passive', sa.BOOLEAN(), nullable=False), + sa.Column('Pet', sa.BOOLEAN(), nullable=False), + sa.Column('Level', sa.INTEGER(), nullable=False), + sa.Column('SkillCondition', sa.INTEGER(), nullable=False), + sa.Column('CoolTime', sa.INTEGER(), nullable=False), + sa.Column('SkillIcon', sa.VARCHAR(length=255), nullable=False), + sa.Column('FriendSkillIcon', sa.VARCHAR(length=255), nullable=False), + sa.Column('InfoText', sa.VARCHAR(length=255), nullable=False), + sa.Column('InfoText_en', sa.VARCHAR(length=255), nullable=True), + sa.PrimaryKeyConstraint('SkillId'), + mysql_charset='utf8mb4' + ) + op.create_table('sao_static_trace_table', + sa.Column('PlayerTraceTableId', sa.BIGINT(), nullable=False), + sa.Column('PlayerTraceTableSubId', sa.INTEGER(), nullable=False), + sa.Column('CommonRewardType', sa.INTEGER(), nullable=False), + sa.Column('CommonRewardId', sa.INTEGER(), nullable=False), + sa.Column('CommonRewardNum', sa.INTEGER(), nullable=False), + sa.Column('Rate', sa.INTEGER(), nullable=False), + sa.PrimaryKeyConstraint('PlayerTraceTableId'), + mysql_charset='utf8mb4' + ) + op.create_table('sao_player_beginner_mission', + sa.Column('id', sa.BIGINT(), nullable=False), + sa.Column('user', sa.INTEGER(), nullable=False), + sa.Column('beginner_mission_id', sa.INTEGER(), nullable=False), + sa.Column('condition_id', sa.INTEGER(), nullable=False), + sa.Column('is_seat', sa.BOOLEAN(), server_default='0', nullable=False), + sa.Column('achievement_num', sa.INTEGER(), nullable=False), + sa.Column('complete_flag', sa.BOOLEAN(), server_default='0', nullable=False), + sa.Column('complete_date', sa.TIMESTAMP(), nullable=True), + sa.Column('reward_received_flag', sa.BOOLEAN(), server_default='0', nullable=False), + sa.Column('reward_received_date', sa.TIMESTAMP(), nullable=True), + sa.ForeignKeyConstraint(['user'], ['aime_user.id'], onupdate='cascade', ondelete='cascade'), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('user'), + sa.UniqueConstraint('user', 'condition_id', name='sao_player_beginner_mission_uk'), + mysql_charset='utf8mb4' + ) op.create_table('sao_player_resource_card', sa.Column('id', sa.BIGINT(), nullable=False), sa.Column('user', sa.INTEGER(), nullable=False), @@ -159,11 +211,22 @@ def upgrade(): sa.UniqueConstraint('user', 'quest_scene_id', 'ex_bonus_table_id', name='sao_player_ex_bonus_uk'), mysql_charset='utf8mb4' ) + op.create_table('sao_player_hero_card', + sa.Column('id', sa.BIGINT(), nullable=False), + sa.Column('user', sa.INTEGER(), nullable=False), + sa.Column('user_hero_id', sa.INTEGER(), nullable=False), + sa.Column('holographic_flag', sa.BOOLEAN(), server_default='0', nullable=False), + sa.Column('serial', sa.VARCHAR(length=20), nullable=True), + sa.ForeignKeyConstraint(['user'], ['aime_user.id'], onupdate='cascade', ondelete='cascade'), + sa.ForeignKeyConstraint(['user_hero_id'], ['sao_hero_log_data.id'], onupdate='cascade', ondelete='cascade'), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('serial'), + mysql_charset='utf8mb4' + ) op.alter_column('sao_end_sessions', 'play_date', existing_type=mysql.TIMESTAMP(), server_default=sa.text('now()'), existing_nullable=False) - op.alter_column("sao_equipment_data", "equipment_id", existing_type=sa.Integer(), type_=sa.BIGINT()) op.add_column('sao_equipment_data', sa.Column('is_shop_purchase', sa.BOOLEAN(), server_default='0', nullable=False)) op.add_column('sao_equipment_data', sa.Column('is_protect', sa.BOOLEAN(), server_default='0', nullable=False)) op.add_column('sao_equipment_data', sa.Column('property1_property_id', sa.BIGINT(), server_default='2', nullable=False)) @@ -179,18 +242,118 @@ def upgrade(): op.add_column('sao_equipment_data', sa.Column('property4_value1', sa.INTEGER(), server_default='0', nullable=False)) op.add_column('sao_equipment_data', sa.Column('property4_value2', sa.INTEGER(), server_default='0', nullable=False)) op.add_column('sao_equipment_data', sa.Column('converted_card_num', sa.INTEGER(), server_default='0', nullable=False)) + op.alter_column('sao_equipment_data', 'equipment_id', + existing_type=mysql.INTEGER(), + type_=sa.BIGINT(), + existing_nullable=False) op.alter_column('sao_equipment_data', 'get_date', existing_type=mysql.TIMESTAMP(), server_default=sa.text('now()'), existing_nullable=False) - op.drop_column('sao_static_equipment_list', 'id') - op.drop_column('sao_static_equipment_list', 'name') - op.drop_column('sao_static_equipment_list', 'flavorText') - op.drop_column('sao_static_equipment_list', 'equipmentId') - op.drop_column('sao_static_equipment_list', 'rarity') - op.drop_column('sao_static_equipment_list', 'enabled') - op.drop_column('sao_static_equipment_list', 'weaponTypeId') - op.drop_column('sao_static_equipment_list', 'equipmentType') + op.create_foreign_key(None, 'sao_equipment_data', 'sao_static_property', ['property2_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_equipment_data', 'sao_static_property', ['property4_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_equipment_data', 'sao_static_property', ['property3_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_equipment_data', 'sao_static_property', ['property1_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_equipment_data', 'sao_static_equipment_list', ['equipment_id'], ['EquipmentId'], onupdate='cascade', ondelete='cascade') + op.add_column('sao_hero_log_data', sa.Column('hero_log_id', sa.BIGINT(), nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('max_level_extend_num', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('is_awakenable', sa.BOOLEAN(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('awakening_stage', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('awakening_exp', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('is_shop_purchase', sa.BOOLEAN(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('is_protect', sa.BOOLEAN(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property1_property_id', sa.BIGINT(), server_default='2', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property1_value1', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property1_value2', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property2_property_id', sa.BIGINT(), server_default='2', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property2_value1', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property2_value2', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property3_property_id', sa.BIGINT(), server_default='2', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property3_value1', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property3_value2', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property4_property_id', sa.BIGINT(), server_default='2', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property4_value1', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property4_value2', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('converted_card_num', sa.INTEGER(), server_default='0', nullable=False)) + op.alter_column('sao_hero_log_data', 'main_weapon', + existing_type=mysql.INTEGER(), + type_=sa.BIGINT(), + nullable=True) + op.alter_column('sao_hero_log_data', 'sub_equipment', + existing_type=mysql.INTEGER(), + type_=sa.BIGINT(), + nullable=True) + op.alter_column('sao_hero_log_data', 'skill_slot1_skill_id', + existing_type=mysql.INTEGER(), + type_=sa.BIGINT(), + nullable=True) + op.alter_column('sao_hero_log_data', 'skill_slot2_skill_id', + existing_type=mysql.INTEGER(), + type_=sa.BIGINT(), + nullable=True) + op.alter_column('sao_hero_log_data', 'skill_slot3_skill_id', + existing_type=mysql.INTEGER(), + type_=sa.BIGINT(), + nullable=True) + op.alter_column('sao_hero_log_data', 'skill_slot4_skill_id', + existing_type=mysql.INTEGER(), + type_=sa.BIGINT(), + nullable=True) + op.alter_column('sao_hero_log_data', 'skill_slot5_skill_id', + existing_type=mysql.INTEGER(), + type_=sa.BIGINT(), + nullable=True) + op.alter_column('sao_hero_log_data', 'get_date', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('now()'), + existing_nullable=False) + op.drop_constraint('sao_hero_log_data_uk', 'sao_hero_log_data', type_='unique') + op.create_unique_constraint('sao_hero_log_data_uk', 'sao_hero_log_data', ['user', 'hero_log_id']) + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_property', ['property4_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_skill', ['skill_slot1_skill_id'], ['SkillId'], onupdate='set null', ondelete='set null') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_skill', ['skill_slot5_skill_id'], ['SkillId'], onupdate='set null', ondelete='set null') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_skill', ['skill_slot2_skill_id'], ['SkillId'], onupdate='set null', ondelete='set null') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_skill', ['skill_slot3_skill_id'], ['SkillId'], onupdate='set null', ondelete='set null') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_equipment_data', ['main_weapon'], ['id'], onupdate='set null', ondelete='set null') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_property', ['property3_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_skill', ['skill_slot4_skill_id'], ['SkillId'], onupdate='set null', ondelete='set null') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_equipment_data', ['sub_equipment'], ['id'], onupdate='set null', ondelete='set null') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_property', ['property1_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_hero_list', ['hero_log_id'], ['HeroLogId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_property', ['property2_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.drop_column('sao_hero_log_data', 'user_hero_log_id') + op.create_foreign_key(None, 'sao_hero_party', 'sao_hero_log_data', ['user_hero_log_id_3'], ['id'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_hero_party', 'sao_hero_log_data', ['user_hero_log_id_1'], ['id'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_hero_party', 'sao_hero_log_data', ['user_hero_log_id_2'], ['id'], onupdate='cascade', ondelete='cascade') + op.alter_column('sao_item_data', 'get_date', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('now()'), + existing_nullable=False) + op.alter_column('sao_play_sessions', 'play_date', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('now()'), + existing_nullable=False) + op.add_column('sao_player_quest', sa.Column('quest_type', sa.INTEGER(), server_default='1', nullable=False)) + op.add_column('sao_player_quest', sa.Column('quest_scene_id', sa.BIGINT(), nullable=False)) + op.alter_column('sao_player_quest', 'play_date', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('now()'), + existing_nullable=False) + op.drop_constraint('sao_player_quest_uk', 'sao_player_quest', type_='unique') + op.create_unique_constraint('sao_player_quest_uk', 'sao_player_quest', ['user', 'quest_scene_id']) + op.create_foreign_key(None, 'sao_player_quest', 'sao_static_quest', ['quest_scene_id'], ['QuestSceneId'], onupdate='cascade', ondelete='cascade') + op.drop_column('sao_player_quest', 'episode_id') + op.add_column('sao_profile', sa.Column('my_shop', sa.INTEGER(), nullable=True)) + op.add_column('sao_profile', sa.Column('fav_hero', sa.INTEGER(), nullable=True)) + op.add_column('sao_profile', sa.Column('when_register', sa.TIMESTAMP(), server_default=sa.text('now()'), nullable=True)) + op.add_column('sao_profile', sa.Column('last_login_date', sa.TIMESTAMP(), nullable=True)) + op.add_column('sao_profile', sa.Column('last_yui_medal_date', sa.TIMESTAMP(), nullable=True)) + op.add_column('sao_profile', sa.Column('last_bonus_yui_medal_date', sa.TIMESTAMP(), nullable=True)) + op.add_column('sao_profile', sa.Column('last_comeback_date', sa.TIMESTAMP(), nullable=True)) + op.add_column('sao_profile', sa.Column('last_login_bonus_date', sa.TIMESTAMP(), nullable=True)) + op.add_column('sao_profile', sa.Column('ad_confirm_date', sa.TIMESTAMP(), nullable=True)) + op.add_column('sao_profile', sa.Column('login_ct', sa.INTEGER(), server_default='0', nullable=True)) + op.create_foreign_key(None, 'sao_profile', 'sao_hero_log_data', ['fav_hero'], ['id'], onupdate='cascade', ondelete='set null') op.add_column('sao_static_equipment_list', sa.Column('EquipmentId', sa.BIGINT(), nullable=False)) op.add_column('sao_static_equipment_list', sa.Column('EquipmentType', sa.INTEGER(), nullable=False)) op.add_column('sao_static_equipment_list', sa.Column('WeaponTypeId', sa.INTEGER(), nullable=False)) @@ -217,92 +380,20 @@ def upgrade(): op.add_column('sao_static_equipment_list', sa.Column('AwakeningExp', sa.INTEGER(), nullable=False)) op.add_column('sao_static_equipment_list', sa.Column('FlavorText', sa.VARCHAR(length=255), nullable=False)) op.add_column('sao_static_equipment_list', sa.Column('FlavorText_en', sa.VARCHAR(length=255), nullable=True)) - op.create_primary_key(None, "sao_static_equipment_list", ['EquipmentId']) op.drop_index('sao_static_equipment_list_uk', table_name='sao_static_equipment_list') - op.create_foreign_key(None, 'sao_static_equipment_list', 'sao_static_property', ['Property1PropertyId'], ['PropertyId'], onupdate='cascade', ondelete='cascade') op.create_foreign_key(None, 'sao_static_equipment_list', 'sao_static_property', ['Property2PropertyId'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_static_equipment_list', 'sao_static_property', ['Property1PropertyId'], ['PropertyId'], onupdate='cascade', ondelete='cascade') op.create_foreign_key(None, 'sao_static_equipment_list', 'sao_static_property', ['Property3PropertyId'], ['PropertyId'], onupdate='cascade', ondelete='cascade') op.create_foreign_key(None, 'sao_static_equipment_list', 'sao_static_property', ['Property4PropertyId'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.drop_column('sao_static_equipment_list', 'id') + op.drop_column('sao_static_equipment_list', 'enabled') op.drop_column('sao_static_equipment_list', 'version') - op.create_foreign_key(None, 'sao_equipment_data', 'sao_static_equipment_list', ['equipment_id'], ['EquipmentId'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_equipment_data', 'sao_static_property', ['property1_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_equipment_data', 'sao_static_property', ['property4_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_equipment_data', 'sao_static_property', ['property2_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_equipment_data', 'sao_static_property', ['property3_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') - op.add_column('sao_hero_log_data', sa.Column('hero_log_id', sa.BIGINT(), nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('max_level_extend_num', sa.INTEGER(), server_default='0', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('is_awakenable', sa.BOOLEAN(), server_default='0', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('awakening_stage', sa.INTEGER(), server_default='0', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('awakening_exp', sa.INTEGER(), server_default='0', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('is_shop_purchase', sa.BOOLEAN(), server_default='0', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('is_protect', sa.BOOLEAN(), server_default='0', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('property1_property_id', sa.BIGINT(), server_default='2', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('property1_value1', sa.INTEGER(), server_default='0', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('property1_value2', sa.INTEGER(), server_default='0', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('property2_property_id', sa.BIGINT(), server_default='2', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('property2_value1', sa.INTEGER(), server_default='0', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('property2_value2', sa.INTEGER(), server_default='0', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('property3_property_id', sa.BIGINT(), server_default='2', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('property3_value1', sa.INTEGER(), server_default='0', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('property3_value2', sa.INTEGER(), server_default='0', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('property4_property_id', sa.BIGINT(), server_default='2', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('property4_value1', sa.INTEGER(), server_default='0', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('property4_value2', sa.INTEGER(), server_default='0', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('converted_card_num', sa.INTEGER(), server_default='0', nullable=False)) - op.alter_column('sao_hero_log_data', 'main_weapon', - existing_type=mysql.INTEGER(display_width=11), - nullable=True) - op.alter_column('sao_hero_log_data', 'sub_equipment', - existing_type=mysql.INTEGER(display_width=11), - nullable=True) - op.alter_column('sao_hero_log_data', 'skill_slot1_skill_id', - existing_type=mysql.INTEGER(display_width=11), - type_=sa.BIGINT(), - nullable=True) - op.alter_column('sao_hero_log_data', 'skill_slot2_skill_id', - existing_type=mysql.INTEGER(display_width=11), - type_=sa.BIGINT(), - nullable=True) - op.alter_column('sao_hero_log_data', 'skill_slot3_skill_id', - existing_type=mysql.INTEGER(display_width=11), - type_=sa.BIGINT(), - nullable=True) - op.alter_column('sao_hero_log_data', 'skill_slot4_skill_id', - existing_type=mysql.INTEGER(display_width=11), - type_=sa.BIGINT(), - nullable=True) - op.alter_column('sao_hero_log_data', 'skill_slot5_skill_id', - existing_type=mysql.INTEGER(display_width=11), - type_=sa.BIGINT(), - nullable=True) - op.alter_column('sao_hero_log_data', 'get_date', - existing_type=mysql.TIMESTAMP(), - server_default=sa.text('now()'), - existing_nullable=False) - op.drop_constraint('sao_hero_log_data_ibfk_1', 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint('sao_hero_log_data_uk', 'sao_hero_log_data', type_='unique') - op.create_unique_constraint('sao_hero_log_data_uk', 'sao_hero_log_data', ['user', 'hero_log_id']) - op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_property', ['property2_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_skill', ['skill_slot2_skill_id'], ['SkillId'], onupdate='set null', ondelete='set null') - op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_property', ['property1_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_skill', ['skill_slot1_skill_id'], ['SkillId'], onupdate='set null', ondelete='set null') - op.create_foreign_key(None, 'sao_hero_log_data', 'sao_equipment_data', ['main_weapon'], ['id'], onupdate='set null', ondelete='set null') - op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_property', ['property4_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_skill', ['skill_slot4_skill_id'], ['SkillId'], onupdate='set null', ondelete='set null') - op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_skill', ['skill_slot3_skill_id'], ['SkillId'], onupdate='set null', ondelete='set null') - op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_property', ['property3_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_hero_log_data', 'sao_equipment_data', ['sub_equipment'], ['id'], onupdate='set null', ondelete='set null') - op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_skill', ['skill_slot5_skill_id'], ['SkillId'], onupdate='set null', ondelete='set null') - op.drop_index('sao_static_hero_list_uk', table_name='sao_static_hero_list') - op.drop_column('sao_static_hero_list', 'name') - op.drop_column('sao_static_hero_list', 'skillTableSubId') - op.drop_column('sao_static_hero_list', 'awakeningExp') - op.drop_column('sao_static_hero_list', 'flavorText') - op.drop_column('sao_static_hero_list', 'heroLogId') - op.drop_column('sao_static_hero_list', 'id') - op.drop_column('sao_static_hero_list', 'rarity') - op.drop_column('sao_static_hero_list', 'version') - op.drop_column('sao_static_hero_list', 'nickname') + op.drop_column('sao_static_equipment_list', 'flavorText') + op.drop_column('sao_static_equipment_list', 'equipmentType') + op.drop_column('sao_static_equipment_list', 'equipmentId') + op.drop_column('sao_static_equipment_list', 'rarity') + op.drop_column('sao_static_equipment_list', 'name') + op.drop_column('sao_static_equipment_list', 'weaponTypeId') op.add_column('sao_static_hero_list', sa.Column('HeroLogId', sa.BIGINT(), nullable=False)) op.add_column('sao_static_hero_list', sa.Column('CharaId', sa.INTEGER(), nullable=False)) op.add_column('sao_static_hero_list', sa.Column('Name', sa.VARCHAR(length=255), nullable=False)) @@ -345,74 +436,23 @@ def upgrade(): op.add_column('sao_static_hero_list', sa.Column('Slot4UnlockLevel', sa.INTEGER(), nullable=False)) op.add_column('sao_static_hero_list', sa.Column('Slot5UnlockLevel', sa.INTEGER(), nullable=False)) op.add_column('sao_static_hero_list', sa.Column('CollectionEmptyFrameDisplayFlag', sa.BOOLEAN(), nullable=False)) + op.drop_index('sao_static_hero_list_uk', table_name='sao_static_hero_list') op.create_foreign_key(None, 'sao_static_hero_list', 'sao_static_equipment_list', ['DefaultEquipmentId1'], ['EquipmentId'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_static_hero_list', 'sao_static_equipment_list', ['DefaultEquipmentId2'], ['EquipmentId'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_static_hero_list', 'sao_static_property', ['Property2PropertyId'], ['PropertyId'], onupdate='cascade', ondelete='cascade') op.create_foreign_key(None, 'sao_static_hero_list', 'sao_static_property', ['Property3PropertyId'], ['PropertyId'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_static_hero_list', 'sao_static_property', ['Property4PropertyId'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_static_hero_list', 'sao_static_equipment_list', ['DefaultEquipmentId2'], ['EquipmentId'], onupdate='cascade', ondelete='cascade') op.create_foreign_key(None, 'sao_static_hero_list', 'sao_static_property', ['Property1PropertyId'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_static_hero_list', 'sao_static_property', ['Property2PropertyId'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_static_hero_list', 'sao_static_property', ['Property4PropertyId'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.drop_column('sao_static_hero_list', 'awakeningExp') + op.drop_column('sao_static_hero_list', 'id') + op.drop_column('sao_static_hero_list', 'nickname') op.drop_column('sao_static_hero_list', 'enabled') - op.create_primary_key(None, "sao_static_hero_list", ["HeroLogId"]) - op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_hero_list', ['hero_log_id'], ['HeroLogId'], onupdate='cascade', ondelete='cascade') - op.drop_column('sao_hero_log_data', 'user_hero_log_id') - op.create_foreign_key(None, 'sao_hero_log_data', 'aime_user', ['user'], ['id'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_hero_party', 'sao_hero_log_data', ['user_hero_log_id_3'], ['id'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_hero_party', 'sao_hero_log_data', ['user_hero_log_id_1'], ['id'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_hero_party', 'sao_hero_log_data', ['user_hero_log_id_2'], ['id'], onupdate='cascade', ondelete='cascade') - op.create_table('sao_player_hero_card', - sa.Column('id', sa.BIGINT(), nullable=False), - sa.Column('user', sa.INTEGER(), nullable=False), - sa.Column('user_hero_id', sa.INTEGER(), nullable=False), - sa.Column('holographic_flag', sa.BOOLEAN(), server_default='0', nullable=False), - sa.Column('serial', sa.VARCHAR(length=20), nullable=True), - sa.ForeignKeyConstraint(['user'], ['aime_user.id'], onupdate='cascade', ondelete='cascade'), - sa.ForeignKeyConstraint(['user_hero_id'], ['sao_hero_log_data.id'], onupdate='cascade', ondelete='cascade'), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('serial'), - mysql_charset='utf8mb4' - ) - op.alter_column('sao_item_data', 'get_date', - existing_type=mysql.TIMESTAMP(), - server_default=sa.text('now()'), - existing_nullable=False) - op.alter_column('sao_play_sessions', 'play_date', - existing_type=mysql.TIMESTAMP(), - server_default=sa.text('now()'), - existing_nullable=False) - op.add_column('sao_player_quest', sa.Column('quest_type', sa.INTEGER(), server_default='1', nullable=False)) - op.add_column('sao_player_quest', sa.Column('quest_scene_id', sa.BIGINT(), nullable=False)) - op.alter_column('sao_player_quest', 'play_date', - existing_type=mysql.TIMESTAMP(), - server_default=sa.text('now()'), - existing_nullable=False) - op.drop_constraint('sao_player_quest_ibfk_1', 'sao_player_quest', type_='foreignkey') - op.drop_constraint('sao_player_quest_uk', 'sao_player_quest', type_='unique') - op.create_unique_constraint('sao_player_quest_uk', 'sao_player_quest', ['user', 'quest_scene_id']) - op.create_foreign_key(None, "sao_player_quest", "aime_user", ['user'], ['id']) - op.create_foreign_key(None, 'sao_player_quest', 'sao_static_quest', ['quest_scene_id'], ['QuestSceneId'], onupdate='cascade', ondelete='cascade') - op.drop_column('sao_player_quest', 'episode_id') - op.add_column('sao_profile', sa.Column('my_shop', sa.INTEGER(), nullable=True)) - op.add_column('sao_profile', sa.Column('fav_hero', sa.INTEGER(), nullable=True)) - op.add_column('sao_profile', sa.Column('when_register', sa.TIMESTAMP(), server_default=sa.text('now()'), nullable=True)) - op.add_column('sao_profile', sa.Column('last_login_date', sa.TIMESTAMP(), nullable=True)) - op.add_column('sao_profile', sa.Column('last_yui_medal_date', sa.TIMESTAMP(), nullable=True)) - op.add_column('sao_profile', sa.Column('last_bonus_yui_medal_date', sa.TIMESTAMP(), nullable=True)) - op.add_column('sao_profile', sa.Column('last_comeback_date', sa.TIMESTAMP(), nullable=True)) - op.add_column('sao_profile', sa.Column('last_login_bonus_date', sa.TIMESTAMP(), nullable=True)) - op.add_column('sao_profile', sa.Column('ad_confirm_date', sa.TIMESTAMP(), nullable=True)) - op.add_column('sao_profile', sa.Column('login_ct', sa.INTEGER(), server_default='0', nullable=True)) - op.alter_column('sao_profile', 'own_vp', - existing_type=mysql.INTEGER(display_width=11), - server_default='0', - existing_nullable=True) - op.create_foreign_key(None, 'sao_profile', 'sao_hero_log_data', ['fav_hero'], ['id'], onupdate='cascade', ondelete='set null') - op.drop_column('sao_static_item_list', 'name') - op.drop_column('sao_static_item_list', 'itemId') - op.drop_column('sao_static_item_list', 'itemTypeId') - op.drop_column('sao_static_item_list', 'flavorText') - op.drop_column('sao_static_item_list', 'id') - op.drop_column('sao_static_item_list', 'rarity') - op.drop_column('sao_static_item_list', 'enabled') + op.drop_column('sao_static_hero_list', 'version') + op.drop_column('sao_static_hero_list', 'flavorText') + op.drop_column('sao_static_hero_list', 'heroLogId') + op.drop_column('sao_static_hero_list', 'skillTableSubId') + op.drop_column('sao_static_hero_list', 'rarity') + op.drop_column('sao_static_hero_list', 'name') op.add_column('sao_static_item_list', sa.Column('ItemId', sa.INTEGER(), nullable=False)) op.add_column('sao_static_item_list', sa.Column('ItemTypeId', sa.INTEGER(), nullable=False)) op.add_column('sao_static_item_list', sa.Column('Name', sa.VARCHAR(length=255), nullable=False)) @@ -430,13 +470,73 @@ def upgrade(): op.add_column('sao_static_item_list', sa.Column('ItemIcon', sa.VARCHAR(length=255), nullable=False)) op.drop_index('sao_static_item_list_uk', table_name='sao_static_item_list') op.create_foreign_key(None, 'sao_static_item_list', 'sao_static_property', ['PropertyId'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.drop_column('sao_static_item_list', 'id') + op.drop_column('sao_static_item_list', 'enabled') op.drop_column('sao_static_item_list', 'version') + op.drop_column('sao_static_item_list', 'flavorText') + op.drop_column('sao_static_item_list', 'rarity') + op.drop_column('sao_static_item_list', 'name') + op.drop_column('sao_static_item_list', 'itemTypeId') + op.drop_column('sao_static_item_list', 'itemId') + op.add_column('sao_static_quest', sa.Column('QuestSceneId', sa.BIGINT(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('SortNo', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('Tutorial', sa.BOOLEAN(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('ColRate', sa.DECIMAL(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('LimitDefault', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('LimitResurrection', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('RewardTableSubId', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('PlayerTraceTableSubId', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('SuccessPlayerExp', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('FailedPlayerExp', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('PairExpRate', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('TrioExpRate', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('SingleRewardVp', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('PairRewardVp', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('TrioRewardVp', sa.INTEGER(), nullable=False)) + op.drop_index('sao_static_quest_uk', table_name='sao_static_quest') + op.drop_column('sao_static_quest', 'id') + op.drop_column('sao_static_quest', 'enabled') + op.drop_column('sao_static_quest', 'version') + op.drop_column('sao_static_quest', 'questSceneId') + op.drop_column('sao_static_quest', 'sortNo') + op.drop_column('sao_static_quest', 'name') # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint("sao_static_item_list_ibfk_1", 'sao_static_item_list', type_='foreignkey') + op.add_column('sao_static_quest', sa.Column('name', mysql.VARCHAR(charset='utf8mb3', collation='utf8mb3_unicode_ci', length=255), nullable=True)) + op.add_column('sao_static_quest', sa.Column('sortNo', mysql.INTEGER(), autoincrement=False, nullable=True)) + op.add_column('sao_static_quest', sa.Column('questSceneId', mysql.INTEGER(), autoincrement=False, nullable=True)) + op.add_column('sao_static_quest', sa.Column('version', mysql.INTEGER(), autoincrement=False, nullable=True)) + op.add_column('sao_static_quest', sa.Column('enabled', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True)) + op.add_column('sao_static_quest', sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False)) + op.create_index('sao_static_quest_uk', 'sao_static_quest', ['version', 'questSceneId'], unique=False) + op.drop_column('sao_static_quest', 'TrioRewardVp') + op.drop_column('sao_static_quest', 'PairRewardVp') + op.drop_column('sao_static_quest', 'SingleRewardVp') + op.drop_column('sao_static_quest', 'TrioExpRate') + op.drop_column('sao_static_quest', 'PairExpRate') + op.drop_column('sao_static_quest', 'FailedPlayerExp') + op.drop_column('sao_static_quest', 'SuccessPlayerExp') + op.drop_column('sao_static_quest', 'PlayerTraceTableSubId') + op.drop_column('sao_static_quest', 'RewardTableSubId') + op.drop_column('sao_static_quest', 'LimitResurrection') + op.drop_column('sao_static_quest', 'LimitDefault') + op.drop_column('sao_static_quest', 'ColRate') + op.drop_column('sao_static_quest', 'Tutorial') + op.drop_column('sao_static_quest', 'SortNo') + op.drop_column('sao_static_quest', 'QuestSceneId') + op.add_column('sao_static_item_list', sa.Column('itemId', mysql.INTEGER(), autoincrement=False, nullable=True)) + op.add_column('sao_static_item_list', sa.Column('itemTypeId', mysql.INTEGER(), autoincrement=False, nullable=True)) + op.add_column('sao_static_item_list', sa.Column('name', mysql.VARCHAR(charset='utf8mb3', collation='utf8mb3_unicode_ci', length=255), nullable=True)) + op.add_column('sao_static_item_list', sa.Column('rarity', mysql.INTEGER(), autoincrement=False, nullable=True)) + op.add_column('sao_static_item_list', sa.Column('flavorText', mysql.VARCHAR(charset='utf8mb3', collation='utf8mb3_unicode_ci', length=255), nullable=True)) + op.add_column('sao_static_item_list', sa.Column('version', mysql.INTEGER(), autoincrement=False, nullable=True)) + op.add_column('sao_static_item_list', sa.Column('enabled', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True)) + op.add_column('sao_static_item_list', sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False)) + op.drop_constraint(None, 'sao_static_item_list', type_='foreignkey') + op.create_index('sao_static_item_list_uk', 'sao_static_item_list', ['version', 'itemId'], unique=False) op.drop_column('sao_static_item_list', 'ItemIcon') op.drop_column('sao_static_item_list', 'SalePrice') op.drop_column('sao_static_item_list', 'FlavorText_en') @@ -448,87 +548,27 @@ def downgrade(): op.drop_column('sao_static_item_list', 'PropertyId') op.drop_column('sao_static_item_list', 'Value') op.drop_column('sao_static_item_list', 'Rarity') + op.drop_column('sao_static_item_list', 'Name_en') op.drop_column('sao_static_item_list', 'Name') op.drop_column('sao_static_item_list', 'ItemTypeId') op.drop_column('sao_static_item_list', 'ItemId') - op.add_column('sao_static_item_list', sa.Column('version', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) - op.add_column('sao_static_item_list', sa.Column('enabled', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True)) - op.add_column('sao_static_item_list', sa.Column('rarity', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) - op.add_column('sao_static_item_list', sa.Column('id', mysql.INTEGER(display_width=11), autoincrement=True, nullable=False)) - op.add_column('sao_static_item_list', sa.Column('flavorText', mysql.VARCHAR(length=255), nullable=True)) - op.add_column('sao_static_item_list', sa.Column('itemTypeId', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) - op.add_column('sao_static_item_list', sa.Column('itemId', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) - op.add_column('sao_static_item_list', sa.Column('name', mysql.VARCHAR(length=255), nullable=True)) - op.add_column('sao_hero_log_data', sa.Column('user_hero_log_id', mysql.INTEGER(display_width=11), autoincrement=False, nullable=False)) - op.drop_column('sao_static_item_list', 'Name_en') - op.create_index('sao_static_item_list_uk', 'sao_static_item_list', ['version', 'itemId'], unique=True) - op.drop_constraint("sao_hero_log_data_ibfk_1", 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint("sao_hero_log_data_ibfk_2", 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint("sao_hero_log_data_ibfk_3", 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint("sao_hero_log_data_ibfk_4", 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint("sao_hero_log_data_ibfk_5", 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint("sao_hero_log_data_ibfk_6", 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint("sao_hero_log_data_ibfk_7", 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint("sao_hero_log_data_ibfk_8", 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint("sao_hero_log_data_ibfk_9", 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint("sao_hero_log_data_ibfk_10", 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint("sao_hero_log_data_ibfk_11", 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint("sao_hero_log_data_ibfk_12", 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint("sao_hero_log_data_ibfk_13", 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint('sao_hero_log_data_uk', 'sao_hero_log_data', type_='unique') - op.create_foreign_key("sao_hero_log_data_ibfk_1", 'sao_hero_log_data', "aime_user", ['user'], ['id']) - op.drop_column('sao_hero_log_data', 'converted_card_num') - op.drop_column('sao_hero_log_data', 'property4_value2') - op.drop_column('sao_hero_log_data', 'property4_value1') - op.drop_column('sao_hero_log_data', 'property4_property_id') - op.drop_column('sao_hero_log_data', 'property3_value2') - op.drop_column('sao_hero_log_data', 'property3_value1') - op.drop_column('sao_hero_log_data', 'property3_property_id') - op.drop_column('sao_hero_log_data', 'property2_value2') - op.drop_column('sao_hero_log_data', 'property2_value1') - op.drop_column('sao_hero_log_data', 'property2_property_id') - op.drop_column('sao_hero_log_data', 'property1_value2') - op.drop_column('sao_hero_log_data', 'property1_value1') - op.drop_column('sao_hero_log_data', 'property1_property_id') - op.drop_column('sao_hero_log_data', 'is_protect') - op.drop_column('sao_hero_log_data', 'is_shop_purchase') - op.drop_column('sao_hero_log_data', 'awakening_exp') - op.drop_column('sao_hero_log_data', 'awakening_stage') - op.drop_column('sao_hero_log_data', 'is_awakenable') - op.drop_column('sao_hero_log_data', 'max_level_extend_num') - op.drop_column('sao_hero_log_data', 'hero_log_id') - op.alter_column('sao_hero_log_data', 'get_date', - existing_type=mysql.TIMESTAMP(), - server_default=sa.text('CURRENT_TIMESTAMP'), - existing_nullable=False) - op.alter_column('sao_hero_log_data', 'skill_slot5_skill_id', - existing_type=mysql.INTEGER(display_width=11), - nullable=False) - op.alter_column('sao_hero_log_data', 'skill_slot4_skill_id', - existing_type=mysql.INTEGER(display_width=11), - nullable=False) - op.alter_column('sao_hero_log_data', 'skill_slot3_skill_id', - existing_type=mysql.INTEGER(display_width=11), - nullable=False) - op.alter_column('sao_hero_log_data', 'skill_slot2_skill_id', - existing_type=mysql.INTEGER(display_width=11), - nullable=False) - op.alter_column('sao_hero_log_data', 'skill_slot1_skill_id', - existing_type=mysql.INTEGER(display_width=11), - nullable=False) - op.alter_column('sao_hero_log_data', 'sub_equipment', - existing_type=mysql.INTEGER(display_width=11), - nullable=False) - op.alter_column('sao_hero_log_data', 'main_weapon', - existing_type=mysql.INTEGER(display_width=11), - nullable=False) - op.create_unique_constraint('sao_hero_log_data_uk', 'sao_hero_log_data', ['user', 'user_hero_log_id']) - op.drop_constraint("sao_static_hero_list_ibfk_1", 'sao_static_hero_list', type_='foreignkey') - op.drop_constraint("sao_static_hero_list_ibfk_2", 'sao_static_hero_list', type_='foreignkey') - op.drop_constraint("sao_static_hero_list_ibfk_3", 'sao_static_hero_list', type_='foreignkey') - op.drop_constraint("sao_static_hero_list_ibfk_4", 'sao_static_hero_list', type_='foreignkey') - op.drop_constraint("sao_static_hero_list_ibfk_5", 'sao_static_hero_list', type_='foreignkey') - op.drop_constraint("sao_static_hero_list_ibfk_6", 'sao_static_hero_list', type_='foreignkey') + op.add_column('sao_static_hero_list', sa.Column('name', mysql.VARCHAR(charset='utf8mb3', collation='utf8mb3_unicode_ci', length=255), nullable=True)) + op.add_column('sao_static_hero_list', sa.Column('rarity', mysql.INTEGER(), autoincrement=False, nullable=True)) + op.add_column('sao_static_hero_list', sa.Column('skillTableSubId', mysql.INTEGER(), autoincrement=False, nullable=True)) + op.add_column('sao_static_hero_list', sa.Column('heroLogId', mysql.INTEGER(), autoincrement=False, nullable=True)) + op.add_column('sao_static_hero_list', sa.Column('flavorText', mysql.VARCHAR(charset='utf8mb3', collation='utf8mb3_unicode_ci', length=255), nullable=True)) + op.add_column('sao_static_hero_list', sa.Column('version', mysql.INTEGER(), autoincrement=False, nullable=True)) + op.add_column('sao_static_hero_list', sa.Column('enabled', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True)) + op.add_column('sao_static_hero_list', sa.Column('nickname', mysql.VARCHAR(charset='utf8mb3', collation='utf8mb3_unicode_ci', length=255), nullable=True)) + op.add_column('sao_static_hero_list', sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False)) + op.add_column('sao_static_hero_list', sa.Column('awakeningExp', mysql.INTEGER(), autoincrement=False, nullable=True)) + op.drop_constraint(None, 'sao_static_hero_list', type_='foreignkey') + op.drop_constraint(None, 'sao_static_hero_list', type_='foreignkey') + op.drop_constraint(None, 'sao_static_hero_list', type_='foreignkey') + op.drop_constraint(None, 'sao_static_hero_list', type_='foreignkey') + op.drop_constraint(None, 'sao_static_hero_list', type_='foreignkey') + op.drop_constraint(None, 'sao_static_hero_list', type_='foreignkey') + op.create_index('sao_static_hero_list_uk', 'sao_static_hero_list', ['version', 'heroLogId'], unique=False) op.drop_column('sao_static_hero_list', 'CollectionEmptyFrameDisplayFlag') op.drop_column('sao_static_hero_list', 'Slot5UnlockLevel') op.drop_column('sao_static_hero_list', 'Slot4UnlockLevel') @@ -556,6 +596,7 @@ def downgrade(): op.drop_column('sao_static_hero_list', 'StrMax') op.drop_column('sao_static_hero_list', 'StrMin') op.drop_column('sao_static_hero_list', 'HpMax') + op.drop_column('sao_static_hero_list', 'HpMin') op.drop_column('sao_static_hero_list', 'SkillTableSubId') op.drop_column('sao_static_hero_list', 'DefaultEquipmentId2') op.drop_column('sao_static_hero_list', 'DefaultEquipmentId1') @@ -570,46 +611,20 @@ def downgrade(): op.drop_column('sao_static_hero_list', 'Name') op.drop_column('sao_static_hero_list', 'CharaId') op.drop_column('sao_static_hero_list', 'HeroLogId') - op.add_column('sao_static_hero_list', sa.Column('nickname', mysql.VARCHAR(length=255), nullable=True)) - op.add_column('sao_static_hero_list', sa.Column('version', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) - op.add_column('sao_static_hero_list', sa.Column('enabled', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True)) - op.add_column('sao_static_hero_list', sa.Column('rarity', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) - op.add_column('sao_static_hero_list', sa.Column('id', mysql.INTEGER(display_width=11), autoincrement=True, nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('heroLogId', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) - op.add_column('sao_static_hero_list', sa.Column('flavorText', mysql.VARCHAR(length=255), nullable=True)) - op.add_column('sao_static_hero_list', sa.Column('awakeningExp', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) - op.add_column('sao_static_hero_list', sa.Column('skillTableSubId', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) - op.add_column('sao_static_hero_list', sa.Column('name', mysql.VARCHAR(length=255), nullable=True)) - op.drop_column('sao_static_hero_list', 'HpMin') - op.create_index('sao_static_hero_list_uk', 'sao_static_hero_list', ['version', 'heroLogId'], unique=True) - op.drop_constraint("sao_equipment_data_ibfk_2", 'sao_equipment_data', type_='foreignkey') - op.drop_constraint("sao_equipment_data_ibfk_3", 'sao_equipment_data', type_='foreignkey') - op.drop_constraint("sao_equipment_data_ibfk_4", 'sao_equipment_data', type_='foreignkey') - op.drop_constraint("sao_equipment_data_ibfk_5", 'sao_equipment_data', type_='foreignkey') - op.drop_constraint("sao_equipment_data_ibfk_6", 'sao_equipment_data', type_='foreignkey') - op.alter_column('sao_equipment_data', 'get_date', - existing_type=mysql.TIMESTAMP(), - server_default=sa.text('CURRENT_TIMESTAMP'), - existing_nullable=False) - op.drop_column('sao_equipment_data', 'converted_card_num') - op.drop_column('sao_equipment_data', 'property4_value2') - op.drop_column('sao_equipment_data', 'property4_value1') - op.drop_column('sao_equipment_data', 'property4_property_id') - op.drop_column('sao_equipment_data', 'property3_value2') - op.drop_column('sao_equipment_data', 'property3_value1') - op.drop_column('sao_equipment_data', 'property3_property_id') - op.drop_column('sao_equipment_data', 'property2_value2') - op.drop_column('sao_equipment_data', 'property2_value1') - op.drop_column('sao_equipment_data', 'property2_property_id') - op.drop_column('sao_equipment_data', 'property1_value2') - op.drop_column('sao_equipment_data', 'property1_value1') - op.drop_column('sao_equipment_data', 'property1_property_id') - op.drop_column('sao_equipment_data', 'is_protect') - op.drop_column('sao_equipment_data', 'is_shop_purchase') - op.drop_constraint("sao_static_equipment_list_ibfk_1", 'sao_static_equipment_list', type_='foreignkey') - op.drop_constraint("sao_static_equipment_list_ibfk_2", 'sao_static_equipment_list', type_='foreignkey') - op.drop_constraint("sao_static_equipment_list_ibfk_3", 'sao_static_equipment_list', type_='foreignkey') - op.drop_constraint("sao_static_equipment_list_ibfk_4", 'sao_static_equipment_list', type_='foreignkey') + op.add_column('sao_static_equipment_list', sa.Column('weaponTypeId', mysql.INTEGER(), autoincrement=False, nullable=True)) + op.add_column('sao_static_equipment_list', sa.Column('name', mysql.VARCHAR(charset='utf8mb3', collation='utf8mb3_unicode_ci', length=255), nullable=True)) + op.add_column('sao_static_equipment_list', sa.Column('rarity', mysql.INTEGER(), autoincrement=False, nullable=True)) + op.add_column('sao_static_equipment_list', sa.Column('equipmentId', mysql.INTEGER(), autoincrement=False, nullable=True)) + op.add_column('sao_static_equipment_list', sa.Column('equipmentType', mysql.INTEGER(), autoincrement=False, nullable=True)) + op.add_column('sao_static_equipment_list', sa.Column('flavorText', mysql.VARCHAR(charset='utf8mb3', collation='utf8mb3_unicode_ci', length=255), nullable=True)) + op.add_column('sao_static_equipment_list', sa.Column('version', mysql.INTEGER(), autoincrement=False, nullable=True)) + op.add_column('sao_static_equipment_list', sa.Column('enabled', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True)) + op.add_column('sao_static_equipment_list', sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False)) + op.drop_constraint(None, 'sao_static_equipment_list', type_='foreignkey') + op.drop_constraint(None, 'sao_static_equipment_list', type_='foreignkey') + op.drop_constraint(None, 'sao_static_equipment_list', type_='foreignkey') + op.drop_constraint(None, 'sao_static_equipment_list', type_='foreignkey') + op.create_index('sao_static_equipment_list_uk', 'sao_static_equipment_list', ['version', 'equipmentId'], unique=False) op.drop_column('sao_static_equipment_list', 'FlavorText_en') op.drop_column('sao_static_equipment_list', 'FlavorText') op.drop_column('sao_static_equipment_list', 'AwakeningExp') @@ -631,26 +646,12 @@ def downgrade(): op.drop_column('sao_static_equipment_list', 'StrengthIncrement') op.drop_column('sao_static_equipment_list', 'Power') op.drop_column('sao_static_equipment_list', 'Rarity') + op.drop_column('sao_static_equipment_list', 'Name_en') op.drop_column('sao_static_equipment_list', 'Name') op.drop_column('sao_static_equipment_list', 'WeaponTypeId') op.drop_column('sao_static_equipment_list', 'EquipmentType') op.drop_column('sao_static_equipment_list', 'EquipmentId') - op.add_column('sao_static_equipment_list', sa.Column('equipmentType', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) - op.add_column('sao_static_equipment_list', sa.Column('version', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) - op.add_column('sao_static_equipment_list', sa.Column('weaponTypeId', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) - op.add_column('sao_static_equipment_list', sa.Column('enabled', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True)) - op.add_column('sao_static_equipment_list', sa.Column('rarity', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) - op.add_column('sao_static_equipment_list', sa.Column('equipmentId', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) - op.add_column('sao_static_equipment_list', sa.Column('id', mysql.INTEGER(display_width=11), autoincrement=True, nullable=False)) - op.add_column('sao_static_equipment_list', sa.Column('flavorText', mysql.VARCHAR(length=255), nullable=True)) - op.add_column('sao_static_equipment_list', sa.Column('name', mysql.VARCHAR(length=255), nullable=True)) - op.drop_column('sao_static_equipment_list', 'Name_en') - op.create_index('sao_static_equipment_list_uk', 'sao_static_equipment_list', ['version', 'equipmentId'], unique=True) - op.drop_constraint("sao_profile_ibfk_2", 'sao_profile', type_='foreignkey') - op.alter_column('sao_profile', 'own_vp', - existing_type=mysql.INTEGER(display_width=11), - server_default=sa.text("'300'"), - existing_nullable=True) + op.drop_constraint(None, 'sao_profile', type_='foreignkey') op.drop_column('sao_profile', 'login_ct') op.drop_column('sao_profile', 'ad_confirm_date') op.drop_column('sao_profile', 'last_login_bonus_date') @@ -661,11 +662,9 @@ def downgrade(): op.drop_column('sao_profile', 'when_register') op.drop_column('sao_profile', 'fav_hero') op.drop_column('sao_profile', 'my_shop') - op.add_column('sao_player_quest', sa.Column('episode_id', mysql.INTEGER(display_width=11), autoincrement=False, nullable=False)) - op.drop_constraint("sao_player_quest_ibfk_2", 'sao_player_quest', type_='foreignkey') - op.drop_constraint("sao_player_quest_ibfk_1", 'sao_player_quest', type_='foreignkey') + op.add_column('sao_player_quest', sa.Column('episode_id', mysql.INTEGER(), autoincrement=False, nullable=False)) + op.drop_constraint(None, 'sao_player_quest', type_='foreignkey') op.drop_constraint('sao_player_quest_uk', 'sao_player_quest', type_='unique') - op.create_foreign_key("sao_player_quest_ibfk_1", "sao_player_quest", "aime_user", ['user'], ['id']) op.create_unique_constraint('sao_player_quest_uk', 'sao_player_quest', ['user', 'episode_id']) op.alter_column('sao_player_quest', 'play_date', existing_type=mysql.TIMESTAMP(), @@ -681,9 +680,104 @@ def downgrade(): existing_type=mysql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), existing_nullable=False) - op.drop_constraint("sao_hero_party_ibfk_2", 'sao_hero_party', type_='foreignkey') - op.drop_constraint("sao_hero_party_ibfk_3", 'sao_hero_party', type_='foreignkey') - op.drop_constraint("sao_hero_party_ibfk_4", 'sao_hero_party', type_='foreignkey') + op.drop_constraint(None, 'sao_hero_party', type_='foreignkey') + op.drop_constraint(None, 'sao_hero_party', type_='foreignkey') + op.drop_constraint(None, 'sao_hero_party', type_='foreignkey') + op.add_column('sao_hero_log_data', sa.Column('user_hero_log_id', mysql.INTEGER(), autoincrement=False, nullable=False)) + op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint('sao_hero_log_data_uk', 'sao_hero_log_data', type_='unique') + op.create_unique_constraint('sao_hero_log_data_uk', 'sao_hero_log_data', ['user', 'user_hero_log_id']) + op.alter_column('sao_hero_log_data', 'get_date', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('CURRENT_TIMESTAMP'), + existing_nullable=False) + op.alter_column('sao_hero_log_data', 'skill_slot5_skill_id', + existing_type=sa.BIGINT(), + type_=mysql.INTEGER(), + nullable=False) + op.alter_column('sao_hero_log_data', 'skill_slot4_skill_id', + existing_type=sa.BIGINT(), + type_=mysql.INTEGER(), + nullable=False) + op.alter_column('sao_hero_log_data', 'skill_slot3_skill_id', + existing_type=sa.BIGINT(), + type_=mysql.INTEGER(), + nullable=False) + op.alter_column('sao_hero_log_data', 'skill_slot2_skill_id', + existing_type=sa.BIGINT(), + type_=mysql.INTEGER(), + nullable=False) + op.alter_column('sao_hero_log_data', 'skill_slot1_skill_id', + existing_type=sa.BIGINT(), + type_=mysql.INTEGER(), + nullable=False) + op.alter_column('sao_hero_log_data', 'sub_equipment', + existing_type=sa.BIGINT(), + type_=mysql.INTEGER(), + nullable=False) + op.alter_column('sao_hero_log_data', 'main_weapon', + existing_type=sa.BIGINT(), + type_=mysql.INTEGER(), + nullable=False) + op.drop_column('sao_hero_log_data', 'converted_card_num') + op.drop_column('sao_hero_log_data', 'property4_value2') + op.drop_column('sao_hero_log_data', 'property4_value1') + op.drop_column('sao_hero_log_data', 'property4_property_id') + op.drop_column('sao_hero_log_data', 'property3_value2') + op.drop_column('sao_hero_log_data', 'property3_value1') + op.drop_column('sao_hero_log_data', 'property3_property_id') + op.drop_column('sao_hero_log_data', 'property2_value2') + op.drop_column('sao_hero_log_data', 'property2_value1') + op.drop_column('sao_hero_log_data', 'property2_property_id') + op.drop_column('sao_hero_log_data', 'property1_value2') + op.drop_column('sao_hero_log_data', 'property1_value1') + op.drop_column('sao_hero_log_data', 'property1_property_id') + op.drop_column('sao_hero_log_data', 'is_protect') + op.drop_column('sao_hero_log_data', 'is_shop_purchase') + op.drop_column('sao_hero_log_data', 'awakening_exp') + op.drop_column('sao_hero_log_data', 'awakening_stage') + op.drop_column('sao_hero_log_data', 'is_awakenable') + op.drop_column('sao_hero_log_data', 'max_level_extend_num') + op.drop_column('sao_hero_log_data', 'hero_log_id') + op.drop_constraint(None, 'sao_equipment_data', type_='foreignkey') + op.drop_constraint(None, 'sao_equipment_data', type_='foreignkey') + op.drop_constraint(None, 'sao_equipment_data', type_='foreignkey') + op.drop_constraint(None, 'sao_equipment_data', type_='foreignkey') + op.drop_constraint(None, 'sao_equipment_data', type_='foreignkey') + op.alter_column('sao_equipment_data', 'get_date', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('CURRENT_TIMESTAMP'), + existing_nullable=False) + op.alter_column('sao_equipment_data', 'equipment_id', + existing_type=sa.BIGINT(), + type_=mysql.INTEGER(), + existing_nullable=False) + op.drop_column('sao_equipment_data', 'converted_card_num') + op.drop_column('sao_equipment_data', 'property4_value2') + op.drop_column('sao_equipment_data', 'property4_value1') + op.drop_column('sao_equipment_data', 'property4_property_id') + op.drop_column('sao_equipment_data', 'property3_value2') + op.drop_column('sao_equipment_data', 'property3_value1') + op.drop_column('sao_equipment_data', 'property3_property_id') + op.drop_column('sao_equipment_data', 'property2_value2') + op.drop_column('sao_equipment_data', 'property2_value1') + op.drop_column('sao_equipment_data', 'property2_property_id') + op.drop_column('sao_equipment_data', 'property1_value2') + op.drop_column('sao_equipment_data', 'property1_value1') + op.drop_column('sao_equipment_data', 'property1_property_id') + op.drop_column('sao_equipment_data', 'is_protect') + op.drop_column('sao_equipment_data', 'is_shop_purchase') op.alter_column('sao_end_sessions', 'play_date', existing_type=mysql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), @@ -698,26 +792,9 @@ def downgrade(): op.drop_table('sao_static_episode') op.drop_table('sao_player_tutorial') op.drop_table('sao_player_resource_card') - op.add_column('sao_static_quest', sa.Column('version', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) - op.drop_column('sao_static_quest', 'TrioRewardVp') - op.drop_column('sao_static_quest', 'PairRewardVp') - op.drop_column('sao_static_quest', 'SingleRewardVp') - op.drop_column('sao_static_quest', 'TrioExpRate') - op.drop_column('sao_static_quest', 'PairExpRate') - op.drop_column('sao_static_quest', 'FailedPlayerExp') - op.drop_column('sao_static_quest', 'SuccessPlayerExp') - op.drop_column('sao_static_quest', 'PlayerTraceTableSubId') - op.drop_column('sao_static_quest', 'RewardTableSubId') - op.drop_column('sao_static_quest', 'LimitResurrection') - op.drop_column('sao_static_quest', 'LimitDefault') - op.drop_column('sao_static_quest', 'ColRate') - op.drop_column('sao_static_quest', 'Tutorial') - op.drop_column('sao_static_quest', 'SortNo') - op.drop_column('sao_static_quest', 'QuestSceneId') - op.add_column('sao_static_quest', sa.Column('enabled', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True)) - op.add_column('sao_static_quest', sa.Column('id', mysql.INTEGER(display_width=11), autoincrement=True, nullable=False)) - op.add_column('sao_static_quest', sa.Column('questSceneId', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) - op.add_column('sao_static_quest', sa.Column('sortNo', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) - op.add_column('sao_static_quest', sa.Column('name', mysql.VARCHAR(length=255), nullable=True)) - op.create_index('sao_static_quest_uk', 'sao_static_quest', ['version', 'questSceneId'], unique=True) + op.drop_table('sao_player_beginner_mission') + op.drop_table('sao_static_trace_table') + op.drop_table('sao_static_skill') + op.drop_table('sao_static_reward') + op.drop_table('sao_static_property') # ### end Alembic commands ### From f27f789269ca38f475290f86134e3e438f98e677 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Wed, 26 Jun 2024 22:37:56 -0400 Subject: [PATCH 64/76] sao: alembic sucks sometimes. --- .../versions/a616fd164e40_sao_backport.py | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/core/data/alembic/versions/a616fd164e40_sao_backport.py b/core/data/alembic/versions/a616fd164e40_sao_backport.py index 93b5993..22a9511 100644 --- a/core/data/alembic/versions/a616fd164e40_sao_backport.py +++ b/core/data/alembic/versions/a616fd164e40_sao_backport.py @@ -18,6 +18,28 @@ depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### + op.add_column('sao_static_quest', sa.Column('QuestSceneId', sa.BIGINT(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('SortNo', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('Tutorial', sa.BOOLEAN(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('ColRate', sa.DECIMAL(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('LimitDefault', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('LimitResurrection', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('RewardTableSubId', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('PlayerTraceTableSubId', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('SuccessPlayerExp', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('FailedPlayerExp', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('PairExpRate', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('TrioExpRate', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('SingleRewardVp', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('PairRewardVp', sa.INTEGER(), nullable=False)) + op.add_column('sao_static_quest', sa.Column('TrioRewardVp', sa.INTEGER(), nullable=False)) + op.drop_index('sao_static_quest_uk', table_name='sao_static_quest') + op.drop_column('sao_static_quest', 'id') + op.drop_column('sao_static_quest', 'enabled') + op.drop_column('sao_static_quest', 'version') + op.drop_column('sao_static_quest', 'questSceneId') + op.drop_column('sao_static_quest', 'sortNo') + op.drop_column('sao_static_quest', 'name') op.create_table('sao_static_property', sa.Column('PropertyId', sa.BIGINT(), nullable=False), sa.Column('PropertyTargetType', sa.INTEGER(), nullable=False), @@ -478,28 +500,6 @@ def upgrade(): op.drop_column('sao_static_item_list', 'name') op.drop_column('sao_static_item_list', 'itemTypeId') op.drop_column('sao_static_item_list', 'itemId') - op.add_column('sao_static_quest', sa.Column('QuestSceneId', sa.BIGINT(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('SortNo', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('Tutorial', sa.BOOLEAN(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('ColRate', sa.DECIMAL(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('LimitDefault', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('LimitResurrection', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('RewardTableSubId', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('PlayerTraceTableSubId', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('SuccessPlayerExp', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('FailedPlayerExp', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('PairExpRate', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('TrioExpRate', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('SingleRewardVp', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('PairRewardVp', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('TrioRewardVp', sa.INTEGER(), nullable=False)) - op.drop_index('sao_static_quest_uk', table_name='sao_static_quest') - op.drop_column('sao_static_quest', 'id') - op.drop_column('sao_static_quest', 'enabled') - op.drop_column('sao_static_quest', 'version') - op.drop_column('sao_static_quest', 'questSceneId') - op.drop_column('sao_static_quest', 'sortNo') - op.drop_column('sao_static_quest', 'name') # ### end Alembic commands ### From 5eb2ef83112cc1a18dae62757447244f9f0538d7 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Wed, 26 Jun 2024 23:01:47 -0400 Subject: [PATCH 65/76] sao: schema fixes 3: the misery never ends --- .../versions/a616fd164e40_sao_backport.py | 76 ++++++++----------- 1 file changed, 32 insertions(+), 44 deletions(-) diff --git a/core/data/alembic/versions/a616fd164e40_sao_backport.py b/core/data/alembic/versions/a616fd164e40_sao_backport.py index 22a9511..d216df7 100644 --- a/core/data/alembic/versions/a616fd164e40_sao_backport.py +++ b/core/data/alembic/versions/a616fd164e40_sao_backport.py @@ -18,28 +18,26 @@ depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.add_column('sao_static_quest', sa.Column('QuestSceneId', sa.BIGINT(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('SortNo', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('Tutorial', sa.BOOLEAN(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('ColRate', sa.DECIMAL(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('LimitDefault', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('LimitResurrection', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('RewardTableSubId', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('PlayerTraceTableSubId', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('SuccessPlayerExp', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('FailedPlayerExp', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('PairExpRate', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('TrioExpRate', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('SingleRewardVp', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('PairRewardVp', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_quest', sa.Column('TrioRewardVp', sa.INTEGER(), nullable=False)) - op.drop_index('sao_static_quest_uk', table_name='sao_static_quest') - op.drop_column('sao_static_quest', 'id') - op.drop_column('sao_static_quest', 'enabled') - op.drop_column('sao_static_quest', 'version') - op.drop_column('sao_static_quest', 'questSceneId') - op.drop_column('sao_static_quest', 'sortNo') - op.drop_column('sao_static_quest', 'name') + op.drop_table('sao_static_quest') + op.create_table('sao_static_quest', + sa.Column('QuestSceneId', sa.BIGINT(), nullable=False), + sa.Column('SortNo', sa.INTEGER(), nullable=False), + sa.Column('Tutorial', sa.BOOLEAN(), nullable=False), + sa.Column('ColRate', sa.DECIMAL(), nullable=False), + sa.Column('LimitDefault', sa.INTEGER(), nullable=False), + sa.Column('LimitResurrection', sa.INTEGER(), nullable=False), + sa.Column('RewardTableSubId', sa.INTEGER(), nullable=False), + sa.Column('PlayerTraceTableSubId', sa.INTEGER(), nullable=False), + sa.Column('SuccessPlayerExp', sa.INTEGER(), nullable=False), + sa.Column('FailedPlayerExp', sa.INTEGER(), nullable=False), + sa.Column('PairExpRate', sa.INTEGER(), nullable=False), + sa.Column('TrioExpRate', sa.INTEGER(), nullable=False), + sa.Column('SingleRewardVp', sa.INTEGER(), nullable=False), + sa.Column('PairRewardVp', sa.INTEGER(), nullable=False), + sa.Column('TrioRewardVp', sa.INTEGER(), nullable=False), + sa.PrimaryKeyConstraint('QuestSceneId'), + mysql_charset='utf8mb4' + ) op.create_table('sao_static_property', sa.Column('PropertyId', sa.BIGINT(), nullable=False), sa.Column('PropertyTargetType', sa.INTEGER(), nullable=False), @@ -505,28 +503,6 @@ def upgrade(): def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.add_column('sao_static_quest', sa.Column('name', mysql.VARCHAR(charset='utf8mb3', collation='utf8mb3_unicode_ci', length=255), nullable=True)) - op.add_column('sao_static_quest', sa.Column('sortNo', mysql.INTEGER(), autoincrement=False, nullable=True)) - op.add_column('sao_static_quest', sa.Column('questSceneId', mysql.INTEGER(), autoincrement=False, nullable=True)) - op.add_column('sao_static_quest', sa.Column('version', mysql.INTEGER(), autoincrement=False, nullable=True)) - op.add_column('sao_static_quest', sa.Column('enabled', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True)) - op.add_column('sao_static_quest', sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False)) - op.create_index('sao_static_quest_uk', 'sao_static_quest', ['version', 'questSceneId'], unique=False) - op.drop_column('sao_static_quest', 'TrioRewardVp') - op.drop_column('sao_static_quest', 'PairRewardVp') - op.drop_column('sao_static_quest', 'SingleRewardVp') - op.drop_column('sao_static_quest', 'TrioExpRate') - op.drop_column('sao_static_quest', 'PairExpRate') - op.drop_column('sao_static_quest', 'FailedPlayerExp') - op.drop_column('sao_static_quest', 'SuccessPlayerExp') - op.drop_column('sao_static_quest', 'PlayerTraceTableSubId') - op.drop_column('sao_static_quest', 'RewardTableSubId') - op.drop_column('sao_static_quest', 'LimitResurrection') - op.drop_column('sao_static_quest', 'LimitDefault') - op.drop_column('sao_static_quest', 'ColRate') - op.drop_column('sao_static_quest', 'Tutorial') - op.drop_column('sao_static_quest', 'SortNo') - op.drop_column('sao_static_quest', 'QuestSceneId') op.add_column('sao_static_item_list', sa.Column('itemId', mysql.INTEGER(), autoincrement=False, nullable=True)) op.add_column('sao_static_item_list', sa.Column('itemTypeId', mysql.INTEGER(), autoincrement=False, nullable=True)) op.add_column('sao_static_item_list', sa.Column('name', mysql.VARCHAR(charset='utf8mb3', collation='utf8mb3_unicode_ci', length=255), nullable=True)) @@ -797,4 +773,16 @@ def downgrade(): op.drop_table('sao_static_skill') op.drop_table('sao_static_reward') op.drop_table('sao_static_property') + op.drop_table('sao_static_quest') + op.create_table('sao_static_quest', + sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False), + sa.Column('enabled', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True), + sa.Column('version', mysql.INTEGER(), autoincrement=False, nullable=True), + sa.Column('questSceneId', mysql.INTEGER(), autoincrement=False, nullable=True), + sa.Column('sortNo', mysql.INTEGER(), autoincrement=False, nullable=True), + sa.Column('name', mysql.VARCHAR(charset='utf8mb4', collation='utf8mb4_general_ci', length=255), nullable=True), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint("version", "questSceneId", name="sao_static_quest_uk"), + mysql_charset='utf8mb4' + ) # ### end Alembic commands ### From c13b096190093e2d56f5dc2060293c8f91fec239 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Wed, 26 Jun 2024 23:31:59 -0400 Subject: [PATCH 66/76] sao: split massive upgrade in half and hope it works --- .../680789dabab3_sao_player_changes.py | 277 ++++++++ .../versions/a616fd164e40_sao_backport.py | 653 ++++-------------- titles/sao/schema/item.py | 4 +- 3 files changed, 430 insertions(+), 504 deletions(-) create mode 100644 core/data/alembic/versions/680789dabab3_sao_player_changes.py diff --git a/core/data/alembic/versions/680789dabab3_sao_player_changes.py b/core/data/alembic/versions/680789dabab3_sao_player_changes.py new file mode 100644 index 0000000..414dd60 --- /dev/null +++ b/core/data/alembic/versions/680789dabab3_sao_player_changes.py @@ -0,0 +1,277 @@ +"""sao_player_changes + +Revision ID: 680789dabab3 +Revises: a616fd164e40 +Create Date: 2024-06-26 23:19:16.863778 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = '680789dabab3' +down_revision = 'a616fd164e40' +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column('sao_equipment_data', sa.Column('is_shop_purchase', sa.BOOLEAN(), server_default='0', nullable=False)) + op.add_column('sao_equipment_data', sa.Column('is_protect', sa.BOOLEAN(), server_default='0', nullable=False)) + op.add_column('sao_equipment_data', sa.Column('property1_property_id', sa.BIGINT(), server_default='2', nullable=False)) + op.add_column('sao_equipment_data', sa.Column('property1_value1', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_equipment_data', sa.Column('property1_value2', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_equipment_data', sa.Column('property2_property_id', sa.BIGINT(), server_default='2', nullable=False)) + op.add_column('sao_equipment_data', sa.Column('property2_value1', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_equipment_data', sa.Column('property2_value2', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_equipment_data', sa.Column('property3_property_id', sa.BIGINT(), server_default='2', nullable=False)) + op.add_column('sao_equipment_data', sa.Column('property3_value1', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_equipment_data', sa.Column('property3_value2', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_equipment_data', sa.Column('property4_property_id', sa.BIGINT(), server_default='2', nullable=False)) + op.add_column('sao_equipment_data', sa.Column('property4_value1', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_equipment_data', sa.Column('property4_value2', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_equipment_data', sa.Column('converted_card_num', sa.INTEGER(), server_default='0', nullable=False)) + op.alter_column('sao_equipment_data', 'equipment_id', + existing_type=mysql.INTEGER(), + type_=sa.BIGINT(), + existing_nullable=False) + op.alter_column('sao_equipment_data', 'get_date', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('now()'), + existing_nullable=False) + op.create_foreign_key(None, 'sao_equipment_data', 'sao_static_property', ['property2_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_equipment_data', 'sao_static_property', ['property4_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_equipment_data', 'sao_static_property', ['property3_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_equipment_data', 'sao_static_property', ['property1_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_equipment_data', 'sao_static_equipment_list', ['equipment_id'], ['EquipmentId'], onupdate='cascade', ondelete='cascade') + op.add_column('sao_hero_log_data', sa.Column('hero_log_id', sa.BIGINT(), nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('max_level_extend_num', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('is_awakenable', sa.BOOLEAN(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('awakening_stage', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('awakening_exp', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('is_shop_purchase', sa.BOOLEAN(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('is_protect', sa.BOOLEAN(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property1_property_id', sa.BIGINT(), server_default='2', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property1_value1', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property1_value2', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property2_property_id', sa.BIGINT(), server_default='2', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property2_value1', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property2_value2', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property3_property_id', sa.BIGINT(), server_default='2', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property3_value1', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property3_value2', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property4_property_id', sa.BIGINT(), server_default='2', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property4_value1', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('property4_value2', sa.INTEGER(), server_default='0', nullable=False)) + op.add_column('sao_hero_log_data', sa.Column('converted_card_num', sa.INTEGER(), server_default='0', nullable=False)) + op.alter_column('sao_hero_log_data', 'main_weapon', + existing_type=mysql.INTEGER(), + type_=sa.BIGINT(), + nullable=True) + op.alter_column('sao_hero_log_data', 'sub_equipment', + existing_type=mysql.INTEGER(), + type_=sa.BIGINT(), + nullable=True) + op.alter_column('sao_hero_log_data', 'skill_slot1_skill_id', + existing_type=mysql.INTEGER(), + type_=sa.BIGINT(), + nullable=True) + op.alter_column('sao_hero_log_data', 'skill_slot2_skill_id', + existing_type=mysql.INTEGER(), + type_=sa.BIGINT(), + nullable=True) + op.alter_column('sao_hero_log_data', 'skill_slot3_skill_id', + existing_type=mysql.INTEGER(), + type_=sa.BIGINT(), + nullable=True) + op.alter_column('sao_hero_log_data', 'skill_slot4_skill_id', + existing_type=mysql.INTEGER(), + type_=sa.BIGINT(), + nullable=True) + op.alter_column('sao_hero_log_data', 'skill_slot5_skill_id', + existing_type=mysql.INTEGER(), + type_=sa.BIGINT(), + nullable=True) + op.alter_column('sao_hero_log_data', 'get_date', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('now()'), + existing_nullable=False) + op.drop_constraint('sao_hero_log_data_uk', 'sao_hero_log_data', type_='unique') + op.create_unique_constraint('sao_hero_log_data_uk', 'sao_hero_log_data', ['user', 'hero_log_id']) + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_property', ['property4_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_skill', ['skill_slot1_skill_id'], ['SkillId'], onupdate='set null', ondelete='set null') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_skill', ['skill_slot5_skill_id'], ['SkillId'], onupdate='set null', ondelete='set null') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_skill', ['skill_slot2_skill_id'], ['SkillId'], onupdate='set null', ondelete='set null') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_skill', ['skill_slot3_skill_id'], ['SkillId'], onupdate='set null', ondelete='set null') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_equipment_data', ['main_weapon'], ['id'], onupdate='set null', ondelete='set null') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_property', ['property3_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_skill', ['skill_slot4_skill_id'], ['SkillId'], onupdate='set null', ondelete='set null') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_equipment_data', ['sub_equipment'], ['id'], onupdate='set null', ondelete='set null') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_property', ['property1_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_hero_list', ['hero_log_id'], ['HeroLogId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_property', ['property2_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.drop_column('sao_hero_log_data', 'user_hero_log_id') + op.create_foreign_key(None, 'sao_hero_party', 'sao_hero_log_data', ['user_hero_log_id_3'], ['id'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_hero_party', 'sao_hero_log_data', ['user_hero_log_id_1'], ['id'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, 'sao_hero_party', 'sao_hero_log_data', ['user_hero_log_id_2'], ['id'], onupdate='cascade', ondelete='cascade') + op.alter_column('sao_item_data', 'get_date', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('now()'), + existing_nullable=False) + op.alter_column('sao_play_sessions', 'play_date', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('now()'), + existing_nullable=False) + op.add_column('sao_player_quest', sa.Column('quest_type', sa.INTEGER(), server_default='1', nullable=False)) + op.add_column('sao_player_quest', sa.Column('quest_scene_id', sa.BIGINT(), nullable=False)) + op.alter_column('sao_player_quest', 'play_date', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('now()'), + existing_nullable=False) + op.drop_constraint('sao_player_quest_uk', 'sao_player_quest', type_='unique') + op.create_unique_constraint('sao_player_quest_uk', 'sao_player_quest', ['user', 'quest_scene_id']) + op.create_foreign_key(None, 'sao_player_quest', 'sao_static_quest', ['quest_scene_id'], ['QuestSceneId'], onupdate='cascade', ondelete='cascade') + op.drop_column('sao_player_quest', 'episode_id') + op.add_column('sao_profile', sa.Column('my_shop', sa.INTEGER(), nullable=True)) + op.add_column('sao_profile', sa.Column('fav_hero', sa.INTEGER(), nullable=True)) + op.add_column('sao_profile', sa.Column('when_register', sa.TIMESTAMP(), server_default=sa.text('now()'), nullable=True)) + op.add_column('sao_profile', sa.Column('last_login_date', sa.TIMESTAMP(), nullable=True)) + op.add_column('sao_profile', sa.Column('last_yui_medal_date', sa.TIMESTAMP(), nullable=True)) + op.add_column('sao_profile', sa.Column('last_bonus_yui_medal_date', sa.TIMESTAMP(), nullable=True)) + op.add_column('sao_profile', sa.Column('last_comeback_date', sa.TIMESTAMP(), nullable=True)) + op.add_column('sao_profile', sa.Column('last_login_bonus_date', sa.TIMESTAMP(), nullable=True)) + op.add_column('sao_profile', sa.Column('ad_confirm_date', sa.TIMESTAMP(), nullable=True)) + op.add_column('sao_profile', sa.Column('login_ct', sa.INTEGER(), server_default='0', nullable=True)) + op.create_foreign_key(None, 'sao_profile', 'sao_hero_log_data', ['fav_hero'], ['id'], onupdate='cascade', ondelete='set null') + + +def downgrade(): + + op.drop_constraint(None, 'sao_profile', type_='foreignkey') + op.drop_column('sao_profile', 'login_ct') + op.drop_column('sao_profile', 'ad_confirm_date') + op.drop_column('sao_profile', 'last_login_bonus_date') + op.drop_column('sao_profile', 'last_comeback_date') + op.drop_column('sao_profile', 'last_bonus_yui_medal_date') + op.drop_column('sao_profile', 'last_yui_medal_date') + op.drop_column('sao_profile', 'last_login_date') + op.drop_column('sao_profile', 'when_register') + op.drop_column('sao_profile', 'fav_hero') + op.drop_column('sao_profile', 'my_shop') + op.add_column('sao_player_quest', sa.Column('episode_id', mysql.INTEGER(), autoincrement=False, nullable=False)) + op.drop_constraint(None, 'sao_player_quest', type_='foreignkey') + op.drop_constraint('sao_player_quest_uk', 'sao_player_quest', type_='unique') + op.create_unique_constraint('sao_player_quest_uk', 'sao_player_quest', ['user', 'episode_id']) + op.alter_column('sao_player_quest', 'play_date', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('CURRENT_TIMESTAMP'), + existing_nullable=False) + op.drop_column('sao_player_quest', 'quest_scene_id') + op.drop_column('sao_player_quest', 'quest_type') + op.alter_column('sao_play_sessions', 'play_date', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('CURRENT_TIMESTAMP'), + existing_nullable=False) + op.alter_column('sao_item_data', 'get_date', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('CURRENT_TIMESTAMP'), + existing_nullable=False) + op.drop_constraint(None, 'sao_hero_party', type_='foreignkey') + op.drop_constraint(None, 'sao_hero_party', type_='foreignkey') + op.drop_constraint(None, 'sao_hero_party', type_='foreignkey') + op.add_column('sao_hero_log_data', sa.Column('user_hero_log_id', mysql.INTEGER(), autoincrement=False, nullable=False)) + op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint('sao_hero_log_data_uk', 'sao_hero_log_data', type_='unique') + op.create_unique_constraint('sao_hero_log_data_uk', 'sao_hero_log_data', ['user', 'user_hero_log_id']) + op.alter_column('sao_hero_log_data', 'get_date', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('CURRENT_TIMESTAMP'), + existing_nullable=False) + op.alter_column('sao_hero_log_data', 'skill_slot5_skill_id', + existing_type=sa.BIGINT(), + type_=mysql.INTEGER(), + nullable=False) + op.alter_column('sao_hero_log_data', 'skill_slot4_skill_id', + existing_type=sa.BIGINT(), + type_=mysql.INTEGER(), + nullable=False) + op.alter_column('sao_hero_log_data', 'skill_slot3_skill_id', + existing_type=sa.BIGINT(), + type_=mysql.INTEGER(), + nullable=False) + op.alter_column('sao_hero_log_data', 'skill_slot2_skill_id', + existing_type=sa.BIGINT(), + type_=mysql.INTEGER(), + nullable=False) + op.alter_column('sao_hero_log_data', 'skill_slot1_skill_id', + existing_type=sa.BIGINT(), + type_=mysql.INTEGER(), + nullable=False) + op.alter_column('sao_hero_log_data', 'sub_equipment', + existing_type=sa.BIGINT(), + type_=mysql.INTEGER(), + nullable=False) + op.alter_column('sao_hero_log_data', 'main_weapon', + existing_type=sa.BIGINT(), + type_=mysql.INTEGER(), + nullable=False) + op.drop_column('sao_hero_log_data', 'converted_card_num') + op.drop_column('sao_hero_log_data', 'property4_value2') + op.drop_column('sao_hero_log_data', 'property4_value1') + op.drop_column('sao_hero_log_data', 'property4_property_id') + op.drop_column('sao_hero_log_data', 'property3_value2') + op.drop_column('sao_hero_log_data', 'property3_value1') + op.drop_column('sao_hero_log_data', 'property3_property_id') + op.drop_column('sao_hero_log_data', 'property2_value2') + op.drop_column('sao_hero_log_data', 'property2_value1') + op.drop_column('sao_hero_log_data', 'property2_property_id') + op.drop_column('sao_hero_log_data', 'property1_value2') + op.drop_column('sao_hero_log_data', 'property1_value1') + op.drop_column('sao_hero_log_data', 'property1_property_id') + op.drop_column('sao_hero_log_data', 'is_protect') + op.drop_column('sao_hero_log_data', 'is_shop_purchase') + op.drop_column('sao_hero_log_data', 'awakening_exp') + op.drop_column('sao_hero_log_data', 'awakening_stage') + op.drop_column('sao_hero_log_data', 'is_awakenable') + op.drop_column('sao_hero_log_data', 'max_level_extend_num') + op.drop_column('sao_hero_log_data', 'hero_log_id') + op.drop_constraint(None, 'sao_equipment_data', type_='foreignkey') + op.drop_constraint(None, 'sao_equipment_data', type_='foreignkey') + op.drop_constraint(None, 'sao_equipment_data', type_='foreignkey') + op.drop_constraint(None, 'sao_equipment_data', type_='foreignkey') + op.drop_constraint(None, 'sao_equipment_data', type_='foreignkey') + op.alter_column('sao_equipment_data', 'get_date', + existing_type=mysql.TIMESTAMP(), + server_default=sa.text('CURRENT_TIMESTAMP'), + existing_nullable=False) + op.alter_column('sao_equipment_data', 'equipment_id', + existing_type=sa.BIGINT(), + type_=mysql.INTEGER(), + existing_nullable=False) + op.drop_column('sao_equipment_data', 'converted_card_num') + op.drop_column('sao_equipment_data', 'property4_value2') + op.drop_column('sao_equipment_data', 'property4_value1') + op.drop_column('sao_equipment_data', 'property4_property_id') + op.drop_column('sao_equipment_data', 'property3_value2') + op.drop_column('sao_equipment_data', 'property3_value1') + op.drop_column('sao_equipment_data', 'property3_property_id') + op.drop_column('sao_equipment_data', 'property2_value2') + op.drop_column('sao_equipment_data', 'property2_value1') + op.drop_column('sao_equipment_data', 'property2_property_id') + op.drop_column('sao_equipment_data', 'property1_value2') + op.drop_column('sao_equipment_data', 'property1_value1') + op.drop_column('sao_equipment_data', 'property1_property_id') + op.drop_column('sao_equipment_data', 'is_protect') + op.drop_column('sao_equipment_data', 'is_shop_purchase') diff --git a/core/data/alembic/versions/a616fd164e40_sao_backport.py b/core/data/alembic/versions/a616fd164e40_sao_backport.py index d216df7..5228e0b 100644 --- a/core/data/alembic/versions/a616fd164e40_sao_backport.py +++ b/core/data/alembic/versions/a616fd164e40_sao_backport.py @@ -38,6 +38,7 @@ def upgrade(): sa.PrimaryKeyConstraint('QuestSceneId'), mysql_charset='utf8mb4' ) + op.create_table('sao_static_property', sa.Column('PropertyId', sa.BIGINT(), nullable=False), sa.Column('PropertyTargetType', sa.INTEGER(), nullable=False), @@ -247,513 +248,161 @@ def upgrade(): existing_type=mysql.TIMESTAMP(), server_default=sa.text('now()'), existing_nullable=False) - op.add_column('sao_equipment_data', sa.Column('is_shop_purchase', sa.BOOLEAN(), server_default='0', nullable=False)) - op.add_column('sao_equipment_data', sa.Column('is_protect', sa.BOOLEAN(), server_default='0', nullable=False)) - op.add_column('sao_equipment_data', sa.Column('property1_property_id', sa.BIGINT(), server_default='2', nullable=False)) - op.add_column('sao_equipment_data', sa.Column('property1_value1', sa.INTEGER(), server_default='0', nullable=False)) - op.add_column('sao_equipment_data', sa.Column('property1_value2', sa.INTEGER(), server_default='0', nullable=False)) - op.add_column('sao_equipment_data', sa.Column('property2_property_id', sa.BIGINT(), server_default='2', nullable=False)) - op.add_column('sao_equipment_data', sa.Column('property2_value1', sa.INTEGER(), server_default='0', nullable=False)) - op.add_column('sao_equipment_data', sa.Column('property2_value2', sa.INTEGER(), server_default='0', nullable=False)) - op.add_column('sao_equipment_data', sa.Column('property3_property_id', sa.BIGINT(), server_default='2', nullable=False)) - op.add_column('sao_equipment_data', sa.Column('property3_value1', sa.INTEGER(), server_default='0', nullable=False)) - op.add_column('sao_equipment_data', sa.Column('property3_value2', sa.INTEGER(), server_default='0', nullable=False)) - op.add_column('sao_equipment_data', sa.Column('property4_property_id', sa.BIGINT(), server_default='2', nullable=False)) - op.add_column('sao_equipment_data', sa.Column('property4_value1', sa.INTEGER(), server_default='0', nullable=False)) - op.add_column('sao_equipment_data', sa.Column('property4_value2', sa.INTEGER(), server_default='0', nullable=False)) - op.add_column('sao_equipment_data', sa.Column('converted_card_num', sa.INTEGER(), server_default='0', nullable=False)) - op.alter_column('sao_equipment_data', 'equipment_id', - existing_type=mysql.INTEGER(), - type_=sa.BIGINT(), - existing_nullable=False) - op.alter_column('sao_equipment_data', 'get_date', - existing_type=mysql.TIMESTAMP(), - server_default=sa.text('now()'), - existing_nullable=False) - op.create_foreign_key(None, 'sao_equipment_data', 'sao_static_property', ['property2_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_equipment_data', 'sao_static_property', ['property4_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_equipment_data', 'sao_static_property', ['property3_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_equipment_data', 'sao_static_property', ['property1_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_equipment_data', 'sao_static_equipment_list', ['equipment_id'], ['EquipmentId'], onupdate='cascade', ondelete='cascade') - op.add_column('sao_hero_log_data', sa.Column('hero_log_id', sa.BIGINT(), nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('max_level_extend_num', sa.INTEGER(), server_default='0', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('is_awakenable', sa.BOOLEAN(), server_default='0', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('awakening_stage', sa.INTEGER(), server_default='0', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('awakening_exp', sa.INTEGER(), server_default='0', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('is_shop_purchase', sa.BOOLEAN(), server_default='0', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('is_protect', sa.BOOLEAN(), server_default='0', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('property1_property_id', sa.BIGINT(), server_default='2', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('property1_value1', sa.INTEGER(), server_default='0', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('property1_value2', sa.INTEGER(), server_default='0', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('property2_property_id', sa.BIGINT(), server_default='2', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('property2_value1', sa.INTEGER(), server_default='0', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('property2_value2', sa.INTEGER(), server_default='0', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('property3_property_id', sa.BIGINT(), server_default='2', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('property3_value1', sa.INTEGER(), server_default='0', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('property3_value2', sa.INTEGER(), server_default='0', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('property4_property_id', sa.BIGINT(), server_default='2', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('property4_value1', sa.INTEGER(), server_default='0', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('property4_value2', sa.INTEGER(), server_default='0', nullable=False)) - op.add_column('sao_hero_log_data', sa.Column('converted_card_num', sa.INTEGER(), server_default='0', nullable=False)) - op.alter_column('sao_hero_log_data', 'main_weapon', - existing_type=mysql.INTEGER(), - type_=sa.BIGINT(), - nullable=True) - op.alter_column('sao_hero_log_data', 'sub_equipment', - existing_type=mysql.INTEGER(), - type_=sa.BIGINT(), - nullable=True) - op.alter_column('sao_hero_log_data', 'skill_slot1_skill_id', - existing_type=mysql.INTEGER(), - type_=sa.BIGINT(), - nullable=True) - op.alter_column('sao_hero_log_data', 'skill_slot2_skill_id', - existing_type=mysql.INTEGER(), - type_=sa.BIGINT(), - nullable=True) - op.alter_column('sao_hero_log_data', 'skill_slot3_skill_id', - existing_type=mysql.INTEGER(), - type_=sa.BIGINT(), - nullable=True) - op.alter_column('sao_hero_log_data', 'skill_slot4_skill_id', - existing_type=mysql.INTEGER(), - type_=sa.BIGINT(), - nullable=True) - op.alter_column('sao_hero_log_data', 'skill_slot5_skill_id', - existing_type=mysql.INTEGER(), - type_=sa.BIGINT(), - nullable=True) - op.alter_column('sao_hero_log_data', 'get_date', - existing_type=mysql.TIMESTAMP(), - server_default=sa.text('now()'), - existing_nullable=False) - op.drop_constraint('sao_hero_log_data_uk', 'sao_hero_log_data', type_='unique') - op.create_unique_constraint('sao_hero_log_data_uk', 'sao_hero_log_data', ['user', 'hero_log_id']) - op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_property', ['property4_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_skill', ['skill_slot1_skill_id'], ['SkillId'], onupdate='set null', ondelete='set null') - op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_skill', ['skill_slot5_skill_id'], ['SkillId'], onupdate='set null', ondelete='set null') - op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_skill', ['skill_slot2_skill_id'], ['SkillId'], onupdate='set null', ondelete='set null') - op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_skill', ['skill_slot3_skill_id'], ['SkillId'], onupdate='set null', ondelete='set null') - op.create_foreign_key(None, 'sao_hero_log_data', 'sao_equipment_data', ['main_weapon'], ['id'], onupdate='set null', ondelete='set null') - op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_property', ['property3_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_skill', ['skill_slot4_skill_id'], ['SkillId'], onupdate='set null', ondelete='set null') - op.create_foreign_key(None, 'sao_hero_log_data', 'sao_equipment_data', ['sub_equipment'], ['id'], onupdate='set null', ondelete='set null') - op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_property', ['property1_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_hero_list', ['hero_log_id'], ['HeroLogId'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_property', ['property2_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') - op.drop_column('sao_hero_log_data', 'user_hero_log_id') - op.create_foreign_key(None, 'sao_hero_party', 'sao_hero_log_data', ['user_hero_log_id_3'], ['id'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_hero_party', 'sao_hero_log_data', ['user_hero_log_id_1'], ['id'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_hero_party', 'sao_hero_log_data', ['user_hero_log_id_2'], ['id'], onupdate='cascade', ondelete='cascade') - op.alter_column('sao_item_data', 'get_date', - existing_type=mysql.TIMESTAMP(), - server_default=sa.text('now()'), - existing_nullable=False) - op.alter_column('sao_play_sessions', 'play_date', - existing_type=mysql.TIMESTAMP(), - server_default=sa.text('now()'), - existing_nullable=False) - op.add_column('sao_player_quest', sa.Column('quest_type', sa.INTEGER(), server_default='1', nullable=False)) - op.add_column('sao_player_quest', sa.Column('quest_scene_id', sa.BIGINT(), nullable=False)) - op.alter_column('sao_player_quest', 'play_date', - existing_type=mysql.TIMESTAMP(), - server_default=sa.text('now()'), - existing_nullable=False) - op.drop_constraint('sao_player_quest_uk', 'sao_player_quest', type_='unique') - op.create_unique_constraint('sao_player_quest_uk', 'sao_player_quest', ['user', 'quest_scene_id']) - op.create_foreign_key(None, 'sao_player_quest', 'sao_static_quest', ['quest_scene_id'], ['QuestSceneId'], onupdate='cascade', ondelete='cascade') - op.drop_column('sao_player_quest', 'episode_id') - op.add_column('sao_profile', sa.Column('my_shop', sa.INTEGER(), nullable=True)) - op.add_column('sao_profile', sa.Column('fav_hero', sa.INTEGER(), nullable=True)) - op.add_column('sao_profile', sa.Column('when_register', sa.TIMESTAMP(), server_default=sa.text('now()'), nullable=True)) - op.add_column('sao_profile', sa.Column('last_login_date', sa.TIMESTAMP(), nullable=True)) - op.add_column('sao_profile', sa.Column('last_yui_medal_date', sa.TIMESTAMP(), nullable=True)) - op.add_column('sao_profile', sa.Column('last_bonus_yui_medal_date', sa.TIMESTAMP(), nullable=True)) - op.add_column('sao_profile', sa.Column('last_comeback_date', sa.TIMESTAMP(), nullable=True)) - op.add_column('sao_profile', sa.Column('last_login_bonus_date', sa.TIMESTAMP(), nullable=True)) - op.add_column('sao_profile', sa.Column('ad_confirm_date', sa.TIMESTAMP(), nullable=True)) - op.add_column('sao_profile', sa.Column('login_ct', sa.INTEGER(), server_default='0', nullable=True)) - op.create_foreign_key(None, 'sao_profile', 'sao_hero_log_data', ['fav_hero'], ['id'], onupdate='cascade', ondelete='set null') - op.add_column('sao_static_equipment_list', sa.Column('EquipmentId', sa.BIGINT(), nullable=False)) - op.add_column('sao_static_equipment_list', sa.Column('EquipmentType', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_equipment_list', sa.Column('WeaponTypeId', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_equipment_list', sa.Column('Name', sa.VARCHAR(length=255), nullable=False)) - op.add_column('sao_static_equipment_list', sa.Column('Name_en', sa.VARCHAR(length=255), nullable=True)) - op.add_column('sao_static_equipment_list', sa.Column('Rarity', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_equipment_list', sa.Column('Power', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_equipment_list', sa.Column('StrengthIncrement', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_equipment_list', sa.Column('SkillCondition', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_equipment_list', sa.Column('Property1PropertyId', sa.BIGINT(), nullable=False)) - op.add_column('sao_static_equipment_list', sa.Column('Property1Value1', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_equipment_list', sa.Column('Property1Value2', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_equipment_list', sa.Column('Property2PropertyId', sa.BIGINT(), nullable=False)) - op.add_column('sao_static_equipment_list', sa.Column('Property2Value1', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_equipment_list', sa.Column('Property2Value2', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_equipment_list', sa.Column('Property3PropertyId', sa.BIGINT(), nullable=False)) - op.add_column('sao_static_equipment_list', sa.Column('Property3Value1', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_equipment_list', sa.Column('Property3Value2', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_equipment_list', sa.Column('Property4PropertyId', sa.BIGINT(), nullable=False)) - op.add_column('sao_static_equipment_list', sa.Column('Property4Value1', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_equipment_list', sa.Column('Property4Value2', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_equipment_list', sa.Column('SalePrice', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_equipment_list', sa.Column('CompositionExp', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_equipment_list', sa.Column('AwakeningExp', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_equipment_list', sa.Column('FlavorText', sa.VARCHAR(length=255), nullable=False)) - op.add_column('sao_static_equipment_list', sa.Column('FlavorText_en', sa.VARCHAR(length=255), nullable=True)) - op.drop_index('sao_static_equipment_list_uk', table_name='sao_static_equipment_list') - op.create_foreign_key(None, 'sao_static_equipment_list', 'sao_static_property', ['Property2PropertyId'], ['PropertyId'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_static_equipment_list', 'sao_static_property', ['Property1PropertyId'], ['PropertyId'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_static_equipment_list', 'sao_static_property', ['Property3PropertyId'], ['PropertyId'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_static_equipment_list', 'sao_static_property', ['Property4PropertyId'], ['PropertyId'], onupdate='cascade', ondelete='cascade') - op.drop_column('sao_static_equipment_list', 'id') - op.drop_column('sao_static_equipment_list', 'enabled') - op.drop_column('sao_static_equipment_list', 'version') - op.drop_column('sao_static_equipment_list', 'flavorText') - op.drop_column('sao_static_equipment_list', 'equipmentType') - op.drop_column('sao_static_equipment_list', 'equipmentId') - op.drop_column('sao_static_equipment_list', 'rarity') - op.drop_column('sao_static_equipment_list', 'name') - op.drop_column('sao_static_equipment_list', 'weaponTypeId') - op.add_column('sao_static_hero_list', sa.Column('HeroLogId', sa.BIGINT(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('CharaId', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('Name', sa.VARCHAR(length=255), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('Nickname', sa.VARCHAR(length=255), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('Name_en', sa.VARCHAR(length=255), nullable=True)) - op.add_column('sao_static_hero_list', sa.Column('Nickname_en', sa.VARCHAR(length=255), nullable=True)) - op.add_column('sao_static_hero_list', sa.Column('Rarity', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('WeaponTypeId', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('HeroLogRoleId', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('CostumeTypeId', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('UnitId', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('DefaultEquipmentId1', sa.BIGINT(), nullable=True)) - op.add_column('sao_static_hero_list', sa.Column('DefaultEquipmentId2', sa.BIGINT(), nullable=True)) - op.add_column('sao_static_hero_list', sa.Column('SkillTableSubId', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('HpMin', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('HpMax', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('StrMin', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('StrMax', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('VitMin', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('VitMax', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('IntMin', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('IntMax', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('Property1PropertyId', sa.BIGINT(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('Property1Value1', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('Property1Value2', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('Property2PropertyId', sa.BIGINT(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('Property2Value1', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('Property2Value2', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('Property3PropertyId', sa.BIGINT(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('Property3Value1', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('Property3Value2', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('Property4PropertyId', sa.BIGINT(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('Property4Value1', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('Property4Value2', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('FlavorText', sa.VARCHAR(length=255), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('FlavorText_en', sa.VARCHAR(length=255), nullable=True)) - op.add_column('sao_static_hero_list', sa.Column('SalePrice', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('CompositionExp', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('AwakeningExp', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('Slot4UnlockLevel', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('Slot5UnlockLevel', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('CollectionEmptyFrameDisplayFlag', sa.BOOLEAN(), nullable=False)) - op.drop_index('sao_static_hero_list_uk', table_name='sao_static_hero_list') - op.create_foreign_key(None, 'sao_static_hero_list', 'sao_static_equipment_list', ['DefaultEquipmentId1'], ['EquipmentId'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_static_hero_list', 'sao_static_property', ['Property3PropertyId'], ['PropertyId'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_static_hero_list', 'sao_static_equipment_list', ['DefaultEquipmentId2'], ['EquipmentId'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_static_hero_list', 'sao_static_property', ['Property1PropertyId'], ['PropertyId'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_static_hero_list', 'sao_static_property', ['Property2PropertyId'], ['PropertyId'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, 'sao_static_hero_list', 'sao_static_property', ['Property4PropertyId'], ['PropertyId'], onupdate='cascade', ondelete='cascade') - op.drop_column('sao_static_hero_list', 'awakeningExp') - op.drop_column('sao_static_hero_list', 'id') - op.drop_column('sao_static_hero_list', 'nickname') - op.drop_column('sao_static_hero_list', 'enabled') - op.drop_column('sao_static_hero_list', 'version') - op.drop_column('sao_static_hero_list', 'flavorText') - op.drop_column('sao_static_hero_list', 'heroLogId') - op.drop_column('sao_static_hero_list', 'skillTableSubId') - op.drop_column('sao_static_hero_list', 'rarity') - op.drop_column('sao_static_hero_list', 'name') - op.add_column('sao_static_item_list', sa.Column('ItemId', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_item_list', sa.Column('ItemTypeId', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_item_list', sa.Column('Name', sa.VARCHAR(length=255), nullable=False)) - op.add_column('sao_static_item_list', sa.Column('Name_en', sa.VARCHAR(length=255), nullable=True)) - op.add_column('sao_static_item_list', sa.Column('Rarity', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_item_list', sa.Column('Value', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_item_list', sa.Column('PropertyId', sa.BIGINT(), nullable=False)) - op.add_column('sao_static_item_list', sa.Column('PropertyValue1Min', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_item_list', sa.Column('PropertyValue1Max', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_item_list', sa.Column('PropertyValue2Min', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_item_list', sa.Column('PropertyValue2Max', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_item_list', sa.Column('FlavorText', sa.VARCHAR(length=255), nullable=False)) - op.add_column('sao_static_item_list', sa.Column('FlavorText_en', sa.VARCHAR(length=255), nullable=True)) - op.add_column('sao_static_item_list', sa.Column('SalePrice', sa.INTEGER(), nullable=False)) - op.add_column('sao_static_item_list', sa.Column('ItemIcon', sa.VARCHAR(length=255), nullable=False)) - op.drop_index('sao_static_item_list_uk', table_name='sao_static_item_list') - op.create_foreign_key(None, 'sao_static_item_list', 'sao_static_property', ['PropertyId'], ['PropertyId'], onupdate='cascade', ondelete='cascade') - op.drop_column('sao_static_item_list', 'id') - op.drop_column('sao_static_item_list', 'enabled') - op.drop_column('sao_static_item_list', 'version') - op.drop_column('sao_static_item_list', 'flavorText') - op.drop_column('sao_static_item_list', 'rarity') - op.drop_column('sao_static_item_list', 'name') - op.drop_column('sao_static_item_list', 'itemTypeId') - op.drop_column('sao_static_item_list', 'itemId') + + op.drop_table('sao_static_equipment_list') + op.create_table("sao_static_equipment_list", + sa.Column("EquipmentId", sa.BIGINT, primary_key=True, nullable=False), + sa.Column("EquipmentType", sa.INTEGER, nullable=False), + sa.Column("WeaponTypeId", sa.INTEGER, nullable=False), + sa.Column("Name", sa.VARCHAR(255), nullable=False), + sa.Column("Name_en", sa.VARCHAR(255)), + sa.Column("Rarity", sa.INTEGER, nullable=False), + sa.Column("Power", sa.INTEGER, nullable=False), + sa.Column("StrengthIncrement", sa.INTEGER, nullable=False), + sa.Column("SkillCondition", sa.INTEGER, nullable=False), + sa.Column("Property1PropertyId", sa.BIGINT, sa.ForeignKey("sao_static_property.PropertyId", ondelete="cascade", onupdate="cascade"), nullable=False), + sa.Column("Property1Value1", sa.INTEGER, nullable=False), + sa.Column("Property1Value2", sa.INTEGER, nullable=False), + sa.Column("Property2PropertyId", sa.BIGINT, sa.ForeignKey("sao_static_property.PropertyId", ondelete="cascade", onupdate="cascade"), nullable=False), + sa.Column("Property2Value1", sa.INTEGER, nullable=False), + sa.Column("Property2Value2", sa.INTEGER, nullable=False), + sa.Column("Property3PropertyId", sa.BIGINT, sa.ForeignKey("sao_static_property.PropertyId", ondelete="cascade", onupdate="cascade"), nullable=False), + sa.Column("Property3Value1", sa.INTEGER, nullable=False), + sa.Column("Property3Value2", sa.INTEGER, nullable=False), + sa.Column("Property4PropertyId", sa.BIGINT, sa.ForeignKey("sao_static_property.PropertyId", ondelete="cascade", onupdate="cascade"), nullable=False), + sa.Column("Property4Value1", sa.INTEGER, nullable=False), + sa.Column("Property4Value2", sa.INTEGER, nullable=False), + sa.Column("SalePrice", sa.INTEGER, nullable=False), + sa.Column("CompositionExp", sa.INTEGER, nullable=False), + sa.Column("AwakeningExp", sa.INTEGER, nullable=False), + sa.Column("FlavorText", sa.VARCHAR(255), nullable=False), + sa.Column("FlavorText_en", sa.VARCHAR(255)), + mysql_charset="utf8mb4" + ) + + op.drop_table('sao_static_hero_list') + op.create_table("sao_static_hero_list", + sa.Column("HeroLogId", sa.BIGINT, primary_key=True, nullable=False), + sa.Column("CharaId", sa.INTEGER, nullable=False), + sa.Column("Name", sa.VARCHAR(255), nullable=False), + sa.Column("Nickname", sa.VARCHAR(255), nullable=False), + sa.Column("Name_en", sa.VARCHAR(255)), + sa.Column("Nickname_en", sa.VARCHAR(255)), + sa.Column("Rarity", sa.INTEGER, nullable=False), + sa.Column("WeaponTypeId", sa.INTEGER, nullable=False), + sa.Column("HeroLogRoleId", sa.INTEGER, nullable=False), + sa.Column("CostumeTypeId", sa.INTEGER, nullable=False), + sa.Column("UnitId", sa.INTEGER, nullable=False), + sa.Column("DefaultEquipmentId1", sa.BIGINT, sa.ForeignKey("sao_static_equipment_list.EquipmentId", ondelete="cascade", onupdate="cascade")), + sa.Column("DefaultEquipmentId2", sa.BIGINT, sa.ForeignKey("sao_static_equipment_list.EquipmentId", ondelete="cascade", onupdate="cascade")), + sa.Column("SkillTableSubId", sa.INTEGER, nullable=False), + sa.Column("HpMin", sa.INTEGER, nullable=False), + sa.Column("HpMax", sa.INTEGER, nullable=False), + sa.Column("StrMin", sa.INTEGER, nullable=False), + sa.Column("StrMax", sa.INTEGER, nullable=False), + sa.Column("VitMin", sa.INTEGER, nullable=False), + sa.Column("VitMax", sa.INTEGER, nullable=False), + sa.Column("IntMin", sa.INTEGER, nullable=False), + sa.Column("IntMax", sa.INTEGER, nullable=False), + sa.Column("Property1PropertyId", sa.BIGINT, sa.ForeignKey("sao_static_property.PropertyId", ondelete="cascade", onupdate="cascade"), nullable=False), + sa.Column("Property1Value1", sa.INTEGER, nullable=False), + sa.Column("Property1Value2", sa.INTEGER, nullable=False), + sa.Column("Property2PropertyId", sa.BIGINT, sa.ForeignKey("sao_static_property.PropertyId", ondelete="cascade", onupdate="cascade"), nullable=False), + sa.Column("Property2Value1", sa.INTEGER, nullable=False), + sa.Column("Property2Value2", sa.INTEGER, nullable=False), + sa.Column("Property3PropertyId", sa.BIGINT, sa.ForeignKey("sao_static_property.PropertyId", ondelete="cascade", onupdate="cascade"), nullable=False), + sa.Column("Property3Value1", sa.INTEGER, nullable=False), + sa.Column("Property3Value2", sa.INTEGER, nullable=False), + sa.Column("Property4PropertyId", sa.BIGINT, sa.ForeignKey("sao_static_property.PropertyId", ondelete="cascade", onupdate="cascade"), nullable=False), + sa.Column("Property4Value1", sa.INTEGER, nullable=False), + sa.Column("Property4Value2", sa.INTEGER, nullable=False), + sa.Column("FlavorText", sa.VARCHAR(255), nullable=False), + sa.Column("FlavorText_en", sa.VARCHAR(255)), + sa.Column("SalePrice", sa.INTEGER, nullable=False), + sa.Column("CompositionExp", sa.INTEGER, nullable=False), + sa.Column("AwakeningExp", sa.INTEGER, nullable=False), + sa.Column("Slot4UnlockLevel", sa.INTEGER, nullable=False), + sa.Column("Slot5UnlockLevel", sa.INTEGER, nullable=False), + sa.Column("CollectionEmptyFrameDisplayFlag", sa.BOOLEAN, nullable=False), + mysql_charset="utf8mb4" + ) + + op.drop_table('sao_static_item_list') + op.create_table("sao_static_item_list", + sa.Column("ItemId", sa.INTEGER, nullable=False, primary_key=True), + sa.Column("ItemTypeId", sa.INTEGER, nullable=False), + sa.Column("Name", sa.VARCHAR(255), nullable=False), + sa.Column("Name_en", sa.VARCHAR(255)), + sa.Column("Rarity", sa.INTEGER, nullable=False), + sa.Column("Value", sa.INTEGER, nullable=False), + sa.Column("PropertyId", sa.BIGINT, sa.ForeignKey("sao_static_property.PropertyId", ondelete="cascade", onupdate="cascade"), nullable=False), + sa.Column("PropertyValue1Min", sa.INTEGER, nullable=False), + sa.Column("PropertyValue1Max", sa.INTEGER, nullable=False), + sa.Column("PropertyValue2Min", sa.INTEGER, nullable=False), + sa.Column("PropertyValue2Max", sa.INTEGER, nullable=False), + sa.Column("FlavorText", sa.VARCHAR(255), nullable=False), + sa.Column("FlavorText_en", sa.VARCHAR(255)), + sa.Column("SalePrice", sa.INTEGER, nullable=False), + sa.Column("ItemIcon", sa.VARCHAR(255), nullable=False), + mysql_charset="utf8mb4" + ) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.add_column('sao_static_item_list', sa.Column('itemId', mysql.INTEGER(), autoincrement=False, nullable=True)) - op.add_column('sao_static_item_list', sa.Column('itemTypeId', mysql.INTEGER(), autoincrement=False, nullable=True)) - op.add_column('sao_static_item_list', sa.Column('name', mysql.VARCHAR(charset='utf8mb3', collation='utf8mb3_unicode_ci', length=255), nullable=True)) - op.add_column('sao_static_item_list', sa.Column('rarity', mysql.INTEGER(), autoincrement=False, nullable=True)) - op.add_column('sao_static_item_list', sa.Column('flavorText', mysql.VARCHAR(charset='utf8mb3', collation='utf8mb3_unicode_ci', length=255), nullable=True)) - op.add_column('sao_static_item_list', sa.Column('version', mysql.INTEGER(), autoincrement=False, nullable=True)) - op.add_column('sao_static_item_list', sa.Column('enabled', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True)) - op.add_column('sao_static_item_list', sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False)) - op.drop_constraint(None, 'sao_static_item_list', type_='foreignkey') - op.create_index('sao_static_item_list_uk', 'sao_static_item_list', ['version', 'itemId'], unique=False) - op.drop_column('sao_static_item_list', 'ItemIcon') - op.drop_column('sao_static_item_list', 'SalePrice') - op.drop_column('sao_static_item_list', 'FlavorText_en') - op.drop_column('sao_static_item_list', 'FlavorText') - op.drop_column('sao_static_item_list', 'PropertyValue2Max') - op.drop_column('sao_static_item_list', 'PropertyValue2Min') - op.drop_column('sao_static_item_list', 'PropertyValue1Max') - op.drop_column('sao_static_item_list', 'PropertyValue1Min') - op.drop_column('sao_static_item_list', 'PropertyId') - op.drop_column('sao_static_item_list', 'Value') - op.drop_column('sao_static_item_list', 'Rarity') - op.drop_column('sao_static_item_list', 'Name_en') - op.drop_column('sao_static_item_list', 'Name') - op.drop_column('sao_static_item_list', 'ItemTypeId') - op.drop_column('sao_static_item_list', 'ItemId') - op.add_column('sao_static_hero_list', sa.Column('name', mysql.VARCHAR(charset='utf8mb3', collation='utf8mb3_unicode_ci', length=255), nullable=True)) - op.add_column('sao_static_hero_list', sa.Column('rarity', mysql.INTEGER(), autoincrement=False, nullable=True)) - op.add_column('sao_static_hero_list', sa.Column('skillTableSubId', mysql.INTEGER(), autoincrement=False, nullable=True)) - op.add_column('sao_static_hero_list', sa.Column('heroLogId', mysql.INTEGER(), autoincrement=False, nullable=True)) - op.add_column('sao_static_hero_list', sa.Column('flavorText', mysql.VARCHAR(charset='utf8mb3', collation='utf8mb3_unicode_ci', length=255), nullable=True)) - op.add_column('sao_static_hero_list', sa.Column('version', mysql.INTEGER(), autoincrement=False, nullable=True)) - op.add_column('sao_static_hero_list', sa.Column('enabled', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True)) - op.add_column('sao_static_hero_list', sa.Column('nickname', mysql.VARCHAR(charset='utf8mb3', collation='utf8mb3_unicode_ci', length=255), nullable=True)) - op.add_column('sao_static_hero_list', sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False)) - op.add_column('sao_static_hero_list', sa.Column('awakeningExp', mysql.INTEGER(), autoincrement=False, nullable=True)) - op.drop_constraint(None, 'sao_static_hero_list', type_='foreignkey') - op.drop_constraint(None, 'sao_static_hero_list', type_='foreignkey') - op.drop_constraint(None, 'sao_static_hero_list', type_='foreignkey') - op.drop_constraint(None, 'sao_static_hero_list', type_='foreignkey') - op.drop_constraint(None, 'sao_static_hero_list', type_='foreignkey') - op.drop_constraint(None, 'sao_static_hero_list', type_='foreignkey') - op.create_index('sao_static_hero_list_uk', 'sao_static_hero_list', ['version', 'heroLogId'], unique=False) - op.drop_column('sao_static_hero_list', 'CollectionEmptyFrameDisplayFlag') - op.drop_column('sao_static_hero_list', 'Slot5UnlockLevel') - op.drop_column('sao_static_hero_list', 'Slot4UnlockLevel') - op.drop_column('sao_static_hero_list', 'AwakeningExp') - op.drop_column('sao_static_hero_list', 'CompositionExp') - op.drop_column('sao_static_hero_list', 'SalePrice') - op.drop_column('sao_static_hero_list', 'FlavorText_en') - op.drop_column('sao_static_hero_list', 'FlavorText') - op.drop_column('sao_static_hero_list', 'Property4Value2') - op.drop_column('sao_static_hero_list', 'Property4Value1') - op.drop_column('sao_static_hero_list', 'Property4PropertyId') - op.drop_column('sao_static_hero_list', 'Property3Value2') - op.drop_column('sao_static_hero_list', 'Property3Value1') - op.drop_column('sao_static_hero_list', 'Property3PropertyId') - op.drop_column('sao_static_hero_list', 'Property2Value2') - op.drop_column('sao_static_hero_list', 'Property2Value1') - op.drop_column('sao_static_hero_list', 'Property2PropertyId') - op.drop_column('sao_static_hero_list', 'Property1Value2') - op.drop_column('sao_static_hero_list', 'Property1Value1') - op.drop_column('sao_static_hero_list', 'Property1PropertyId') - op.drop_column('sao_static_hero_list', 'IntMax') - op.drop_column('sao_static_hero_list', 'IntMin') - op.drop_column('sao_static_hero_list', 'VitMax') - op.drop_column('sao_static_hero_list', 'VitMin') - op.drop_column('sao_static_hero_list', 'StrMax') - op.drop_column('sao_static_hero_list', 'StrMin') - op.drop_column('sao_static_hero_list', 'HpMax') - op.drop_column('sao_static_hero_list', 'HpMin') - op.drop_column('sao_static_hero_list', 'SkillTableSubId') - op.drop_column('sao_static_hero_list', 'DefaultEquipmentId2') - op.drop_column('sao_static_hero_list', 'DefaultEquipmentId1') - op.drop_column('sao_static_hero_list', 'UnitId') - op.drop_column('sao_static_hero_list', 'CostumeTypeId') - op.drop_column('sao_static_hero_list', 'HeroLogRoleId') - op.drop_column('sao_static_hero_list', 'WeaponTypeId') - op.drop_column('sao_static_hero_list', 'Rarity') - op.drop_column('sao_static_hero_list', 'Nickname_en') - op.drop_column('sao_static_hero_list', 'Name_en') - op.drop_column('sao_static_hero_list', 'Nickname') - op.drop_column('sao_static_hero_list', 'Name') - op.drop_column('sao_static_hero_list', 'CharaId') - op.drop_column('sao_static_hero_list', 'HeroLogId') - op.add_column('sao_static_equipment_list', sa.Column('weaponTypeId', mysql.INTEGER(), autoincrement=False, nullable=True)) - op.add_column('sao_static_equipment_list', sa.Column('name', mysql.VARCHAR(charset='utf8mb3', collation='utf8mb3_unicode_ci', length=255), nullable=True)) - op.add_column('sao_static_equipment_list', sa.Column('rarity', mysql.INTEGER(), autoincrement=False, nullable=True)) - op.add_column('sao_static_equipment_list', sa.Column('equipmentId', mysql.INTEGER(), autoincrement=False, nullable=True)) - op.add_column('sao_static_equipment_list', sa.Column('equipmentType', mysql.INTEGER(), autoincrement=False, nullable=True)) - op.add_column('sao_static_equipment_list', sa.Column('flavorText', mysql.VARCHAR(charset='utf8mb3', collation='utf8mb3_unicode_ci', length=255), nullable=True)) - op.add_column('sao_static_equipment_list', sa.Column('version', mysql.INTEGER(), autoincrement=False, nullable=True)) - op.add_column('sao_static_equipment_list', sa.Column('enabled', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True)) - op.add_column('sao_static_equipment_list', sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False)) - op.drop_constraint(None, 'sao_static_equipment_list', type_='foreignkey') - op.drop_constraint(None, 'sao_static_equipment_list', type_='foreignkey') - op.drop_constraint(None, 'sao_static_equipment_list', type_='foreignkey') - op.drop_constraint(None, 'sao_static_equipment_list', type_='foreignkey') - op.create_index('sao_static_equipment_list_uk', 'sao_static_equipment_list', ['version', 'equipmentId'], unique=False) - op.drop_column('sao_static_equipment_list', 'FlavorText_en') - op.drop_column('sao_static_equipment_list', 'FlavorText') - op.drop_column('sao_static_equipment_list', 'AwakeningExp') - op.drop_column('sao_static_equipment_list', 'CompositionExp') - op.drop_column('sao_static_equipment_list', 'SalePrice') - op.drop_column('sao_static_equipment_list', 'Property4Value2') - op.drop_column('sao_static_equipment_list', 'Property4Value1') - op.drop_column('sao_static_equipment_list', 'Property4PropertyId') - op.drop_column('sao_static_equipment_list', 'Property3Value2') - op.drop_column('sao_static_equipment_list', 'Property3Value1') - op.drop_column('sao_static_equipment_list', 'Property3PropertyId') - op.drop_column('sao_static_equipment_list', 'Property2Value2') - op.drop_column('sao_static_equipment_list', 'Property2Value1') - op.drop_column('sao_static_equipment_list', 'Property2PropertyId') - op.drop_column('sao_static_equipment_list', 'Property1Value2') - op.drop_column('sao_static_equipment_list', 'Property1Value1') - op.drop_column('sao_static_equipment_list', 'Property1PropertyId') - op.drop_column('sao_static_equipment_list', 'SkillCondition') - op.drop_column('sao_static_equipment_list', 'StrengthIncrement') - op.drop_column('sao_static_equipment_list', 'Power') - op.drop_column('sao_static_equipment_list', 'Rarity') - op.drop_column('sao_static_equipment_list', 'Name_en') - op.drop_column('sao_static_equipment_list', 'Name') - op.drop_column('sao_static_equipment_list', 'WeaponTypeId') - op.drop_column('sao_static_equipment_list', 'EquipmentType') - op.drop_column('sao_static_equipment_list', 'EquipmentId') - op.drop_constraint(None, 'sao_profile', type_='foreignkey') - op.drop_column('sao_profile', 'login_ct') - op.drop_column('sao_profile', 'ad_confirm_date') - op.drop_column('sao_profile', 'last_login_bonus_date') - op.drop_column('sao_profile', 'last_comeback_date') - op.drop_column('sao_profile', 'last_bonus_yui_medal_date') - op.drop_column('sao_profile', 'last_yui_medal_date') - op.drop_column('sao_profile', 'last_login_date') - op.drop_column('sao_profile', 'when_register') - op.drop_column('sao_profile', 'fav_hero') - op.drop_column('sao_profile', 'my_shop') - op.add_column('sao_player_quest', sa.Column('episode_id', mysql.INTEGER(), autoincrement=False, nullable=False)) - op.drop_constraint(None, 'sao_player_quest', type_='foreignkey') - op.drop_constraint('sao_player_quest_uk', 'sao_player_quest', type_='unique') - op.create_unique_constraint('sao_player_quest_uk', 'sao_player_quest', ['user', 'episode_id']) - op.alter_column('sao_player_quest', 'play_date', - existing_type=mysql.TIMESTAMP(), - server_default=sa.text('CURRENT_TIMESTAMP'), - existing_nullable=False) - op.drop_column('sao_player_quest', 'quest_scene_id') - op.drop_column('sao_player_quest', 'quest_type') - op.alter_column('sao_play_sessions', 'play_date', - existing_type=mysql.TIMESTAMP(), - server_default=sa.text('CURRENT_TIMESTAMP'), - existing_nullable=False) - op.alter_column('sao_item_data', 'get_date', - existing_type=mysql.TIMESTAMP(), - server_default=sa.text('CURRENT_TIMESTAMP'), - existing_nullable=False) - op.drop_constraint(None, 'sao_hero_party', type_='foreignkey') - op.drop_constraint(None, 'sao_hero_party', type_='foreignkey') - op.drop_constraint(None, 'sao_hero_party', type_='foreignkey') - op.add_column('sao_hero_log_data', sa.Column('user_hero_log_id', mysql.INTEGER(), autoincrement=False, nullable=False)) - op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint('sao_hero_log_data_uk', 'sao_hero_log_data', type_='unique') - op.create_unique_constraint('sao_hero_log_data_uk', 'sao_hero_log_data', ['user', 'user_hero_log_id']) - op.alter_column('sao_hero_log_data', 'get_date', - existing_type=mysql.TIMESTAMP(), - server_default=sa.text('CURRENT_TIMESTAMP'), - existing_nullable=False) - op.alter_column('sao_hero_log_data', 'skill_slot5_skill_id', - existing_type=sa.BIGINT(), - type_=mysql.INTEGER(), - nullable=False) - op.alter_column('sao_hero_log_data', 'skill_slot4_skill_id', - existing_type=sa.BIGINT(), - type_=mysql.INTEGER(), - nullable=False) - op.alter_column('sao_hero_log_data', 'skill_slot3_skill_id', - existing_type=sa.BIGINT(), - type_=mysql.INTEGER(), - nullable=False) - op.alter_column('sao_hero_log_data', 'skill_slot2_skill_id', - existing_type=sa.BIGINT(), - type_=mysql.INTEGER(), - nullable=False) - op.alter_column('sao_hero_log_data', 'skill_slot1_skill_id', - existing_type=sa.BIGINT(), - type_=mysql.INTEGER(), - nullable=False) - op.alter_column('sao_hero_log_data', 'sub_equipment', - existing_type=sa.BIGINT(), - type_=mysql.INTEGER(), - nullable=False) - op.alter_column('sao_hero_log_data', 'main_weapon', - existing_type=sa.BIGINT(), - type_=mysql.INTEGER(), - nullable=False) - op.drop_column('sao_hero_log_data', 'converted_card_num') - op.drop_column('sao_hero_log_data', 'property4_value2') - op.drop_column('sao_hero_log_data', 'property4_value1') - op.drop_column('sao_hero_log_data', 'property4_property_id') - op.drop_column('sao_hero_log_data', 'property3_value2') - op.drop_column('sao_hero_log_data', 'property3_value1') - op.drop_column('sao_hero_log_data', 'property3_property_id') - op.drop_column('sao_hero_log_data', 'property2_value2') - op.drop_column('sao_hero_log_data', 'property2_value1') - op.drop_column('sao_hero_log_data', 'property2_property_id') - op.drop_column('sao_hero_log_data', 'property1_value2') - op.drop_column('sao_hero_log_data', 'property1_value1') - op.drop_column('sao_hero_log_data', 'property1_property_id') - op.drop_column('sao_hero_log_data', 'is_protect') - op.drop_column('sao_hero_log_data', 'is_shop_purchase') - op.drop_column('sao_hero_log_data', 'awakening_exp') - op.drop_column('sao_hero_log_data', 'awakening_stage') - op.drop_column('sao_hero_log_data', 'is_awakenable') - op.drop_column('sao_hero_log_data', 'max_level_extend_num') - op.drop_column('sao_hero_log_data', 'hero_log_id') - op.drop_constraint(None, 'sao_equipment_data', type_='foreignkey') - op.drop_constraint(None, 'sao_equipment_data', type_='foreignkey') - op.drop_constraint(None, 'sao_equipment_data', type_='foreignkey') - op.drop_constraint(None, 'sao_equipment_data', type_='foreignkey') - op.drop_constraint(None, 'sao_equipment_data', type_='foreignkey') - op.alter_column('sao_equipment_data', 'get_date', - existing_type=mysql.TIMESTAMP(), - server_default=sa.text('CURRENT_TIMESTAMP'), - existing_nullable=False) - op.alter_column('sao_equipment_data', 'equipment_id', - existing_type=sa.BIGINT(), - type_=mysql.INTEGER(), - existing_nullable=False) - op.drop_column('sao_equipment_data', 'converted_card_num') - op.drop_column('sao_equipment_data', 'property4_value2') - op.drop_column('sao_equipment_data', 'property4_value1') - op.drop_column('sao_equipment_data', 'property4_property_id') - op.drop_column('sao_equipment_data', 'property3_value2') - op.drop_column('sao_equipment_data', 'property3_value1') - op.drop_column('sao_equipment_data', 'property3_property_id') - op.drop_column('sao_equipment_data', 'property2_value2') - op.drop_column('sao_equipment_data', 'property2_value1') - op.drop_column('sao_equipment_data', 'property2_property_id') - op.drop_column('sao_equipment_data', 'property1_value2') - op.drop_column('sao_equipment_data', 'property1_value1') - op.drop_column('sao_equipment_data', 'property1_property_id') - op.drop_column('sao_equipment_data', 'is_protect') - op.drop_column('sao_equipment_data', 'is_shop_purchase') + op.drop_table('sao_static_item_list') + op.create_table("sao_static_item_list", + sa.Column("id", sa.Integer, primary_key=True, nullable=False), + sa.Column("version", sa.Integer), + sa.Column("itemId", sa.Integer), + sa.Column("itemTypeId", sa.Integer), + sa.Column("name", sa.String(255)), + sa.Column("rarity", sa.Integer), + sa.Column("flavorText", sa.String(255)), + sa.Column("enabled", sa.Boolean), + sa.UniqueConstraint( + "version", "itemId", name="sao_static_item_list_uk" + ), + mysql_charset="utf8mb4" + ) + + op.drop_table('sao_static_hero_list') + op.create_table("sao_static_hero_list", + sa.Column("id", sa.Integer, primary_key=True, nullable=False), + sa.Column("version", sa.Integer), + sa.Column("heroLogId", sa.Integer), + sa.Column("name", sa.String(255)), + sa.Column("nickname", sa.String(255)), + sa.Column("rarity", sa.Integer), + sa.Column("skillTableSubId", sa.Integer), + sa.Column("awakeningExp", sa.Integer), + sa.Column("flavorText", sa.String(255)), + sa.Column("enabled", sa.Boolean), + sa.UniqueConstraint( + "version", "heroLogId", name="sao_static_hero_list_uk" + ), + mysql_charset="utf8mb4", + ) + + op.drop_table('sao_static_equipment_list') + op.create_table("sao_static_equipment_list", + sa.Column("id", sa.Integer, primary_key=True, nullable=False), + sa.Column("version", sa.Integer), + sa.Column("equipmentId", sa.Integer), + sa.Column("equipmentType", sa.Integer), + sa.Column("weaponTypeId", sa.Integer), + sa.Column("name", sa.String(255)), + sa.Column("rarity", sa.Integer), + sa.Column("flavorText", sa.String(255)), + sa.Column("enabled", sa.Boolean), + sa.UniqueConstraint( + "version", "equipmentId", name="sao_static_equipment_list_uk" + ), + mysql_charset="utf8mb4" + + ) + op.alter_column('sao_end_sessions', 'play_date', existing_type=mysql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), diff --git a/titles/sao/schema/item.py b/titles/sao/schema/item.py index abadca5..b0c58e9 100644 --- a/titles/sao/schema/item.py +++ b/titles/sao/schema/item.py @@ -70,8 +70,8 @@ hero_log_data = Table( Column("hero_log_id", BIGINT, ForeignKey("sao_static_hero_list.HeroLogId", ondelete="cascade", onupdate="cascade"), nullable=False), Column("log_level", Integer, nullable=False), Column("log_exp", Integer, nullable=False), - Column("main_weapon", BIGINT, ForeignKey("sao_equipment_data.id", ondelete="set null", onupdate="set null")), - Column("sub_equipment", BIGINT, ForeignKey("sao_equipment_data.id", ondelete="set null", onupdate="set null")), + Column("main_weapon", INTEGER, ForeignKey("sao_equipment_data.id", ondelete="set null", onupdate="set null")), + Column("sub_equipment", INTEGER, ForeignKey("sao_equipment_data.id", ondelete="set null", onupdate="set null")), Column("skill_slot1_skill_id", BIGINT, ForeignKey("sao_static_skill.SkillId", ondelete="set null", onupdate="set null")), Column("skill_slot2_skill_id", BIGINT, ForeignKey("sao_static_skill.SkillId", ondelete="set null", onupdate="set null")), Column("skill_slot3_skill_id", BIGINT, ForeignKey("sao_static_skill.SkillId", ondelete="set null", onupdate="set null")), From 171131493fdde88c69ea1ea01348f7e7cbfa3183 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Wed, 26 Jun 2024 23:57:07 -0400 Subject: [PATCH 67/76] sao: fix new issues --- .../680789dabab3_sao_player_changes.py | 53 +++++++++++-------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/core/data/alembic/versions/680789dabab3_sao_player_changes.py b/core/data/alembic/versions/680789dabab3_sao_player_changes.py index 414dd60..295a53e 100644 --- a/core/data/alembic/versions/680789dabab3_sao_player_changes.py +++ b/core/data/alembic/versions/680789dabab3_sao_player_changes.py @@ -97,6 +97,7 @@ def upgrade(): existing_type=mysql.TIMESTAMP(), server_default=sa.text('now()'), existing_nullable=False) + op.drop_index('sao_hero_log_data_ibfk_1', 'sao_hero_log_data', type_='foreignkey') op.drop_constraint('sao_hero_log_data_uk', 'sao_hero_log_data', type_='unique') op.create_unique_constraint('sao_hero_log_data_uk', 'sao_hero_log_data', ['user', 'hero_log_id']) op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_property', ['property4_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') @@ -111,6 +112,7 @@ def upgrade(): op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_property', ['property1_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_hero_list', ['hero_log_id'], ['HeroLogId'], onupdate='cascade', ondelete='cascade') op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_property', ['property2_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') + op.create_foreign_key(None, "sao_hero_log_data", "aime_user", ['user'], ['id']) op.drop_column('sao_hero_log_data', 'user_hero_log_id') op.create_foreign_key(None, 'sao_hero_party', 'sao_hero_log_data', ['user_hero_log_id_3'], ['id'], onupdate='cascade', ondelete='cascade') op.create_foreign_key(None, 'sao_hero_party', 'sao_hero_log_data', ['user_hero_log_id_1'], ['id'], onupdate='cascade', ondelete='cascade') @@ -129,8 +131,10 @@ def upgrade(): existing_type=mysql.TIMESTAMP(), server_default=sa.text('now()'), existing_nullable=False) + op.drop_index('sao_player_quest_ibfk_1', 'sao_player_quest', type_='foreignkey') op.drop_constraint('sao_player_quest_uk', 'sao_player_quest', type_='unique') op.create_unique_constraint('sao_player_quest_uk', 'sao_player_quest', ['user', 'quest_scene_id']) + op.create_foreign_key(None, 'sao_player_quest', 'aime_user', ['user'], ['id'], onupdate='cascade', ondelete='cascade') op.create_foreign_key(None, 'sao_player_quest', 'sao_static_quest', ['quest_scene_id'], ['QuestSceneId'], onupdate='cascade', ondelete='cascade') op.drop_column('sao_player_quest', 'episode_id') op.add_column('sao_profile', sa.Column('my_shop', sa.INTEGER(), nullable=True)) @@ -147,8 +151,7 @@ def upgrade(): def downgrade(): - - op.drop_constraint(None, 'sao_profile', type_='foreignkey') + op.drop_constraint("sao_profile_ibfk_2", 'sao_profile', type_='foreignkey') op.drop_column('sao_profile', 'login_ct') op.drop_column('sao_profile', 'ad_confirm_date') op.drop_column('sao_profile', 'last_login_bonus_date') @@ -160,9 +163,11 @@ def downgrade(): op.drop_column('sao_profile', 'fav_hero') op.drop_column('sao_profile', 'my_shop') op.add_column('sao_player_quest', sa.Column('episode_id', mysql.INTEGER(), autoincrement=False, nullable=False)) - op.drop_constraint(None, 'sao_player_quest', type_='foreignkey') + op.drop_constraint("sao_player_quest_ibfk_1", 'sao_player_quest', type_='foreignkey') + op.drop_constraint("sao_player_quest_ibfk_2", 'sao_player_quest', type_='foreignkey') op.drop_constraint('sao_player_quest_uk', 'sao_player_quest', type_='unique') op.create_unique_constraint('sao_player_quest_uk', 'sao_player_quest', ['user', 'episode_id']) + op.create_foreign_key(None, 'sao_player_quest', 'aime_user', ['user'], ['id'], onupdate='cascade', ondelete='cascade') op.alter_column('sao_player_quest', 'play_date', existing_type=mysql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), @@ -177,24 +182,26 @@ def downgrade(): existing_type=mysql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), existing_nullable=False) - op.drop_constraint(None, 'sao_hero_party', type_='foreignkey') - op.drop_constraint(None, 'sao_hero_party', type_='foreignkey') - op.drop_constraint(None, 'sao_hero_party', type_='foreignkey') + op.drop_constraint("sao_hero_party_ibfk_2", 'sao_hero_party', type_='foreignkey') + op.drop_constraint("sao_hero_party_ibfk_3", 'sao_hero_party', type_='foreignkey') + op.drop_constraint("sao_hero_party_ibfk_4", 'sao_hero_party', type_='foreignkey') op.add_column('sao_hero_log_data', sa.Column('user_hero_log_id', mysql.INTEGER(), autoincrement=False, nullable=False)) - op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint(None, 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint("sao_hero_log_data_ibfk_1", 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint("sao_hero_log_data_ibfk_2", 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint("sao_hero_log_data_ibfk_3", 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint("sao_hero_log_data_ibfk_4", 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint("sao_hero_log_data_ibfk_5", 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint("sao_hero_log_data_ibfk_6", 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint("sao_hero_log_data_ibfk_7", 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint("sao_hero_log_data_ibfk_8", 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint("sao_hero_log_data_ibfk_9", 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint("sao_hero_log_data_ibfk_10", 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint("sao_hero_log_data_ibfk_11", 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint("sao_hero_log_data_ibfk_12", 'sao_hero_log_data', type_='foreignkey') + op.drop_constraint("sao_hero_log_data_ibfk_13", 'sao_hero_log_data', type_='foreignkey') op.drop_constraint('sao_hero_log_data_uk', 'sao_hero_log_data', type_='unique') op.create_unique_constraint('sao_hero_log_data_uk', 'sao_hero_log_data', ['user', 'user_hero_log_id']) + op.create_foreign_key(None, "sao_hero_log_data", "aime_user", ['user'], ['id']) op.alter_column('sao_hero_log_data', 'get_date', existing_type=mysql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), @@ -247,11 +254,11 @@ def downgrade(): op.drop_column('sao_hero_log_data', 'is_awakenable') op.drop_column('sao_hero_log_data', 'max_level_extend_num') op.drop_column('sao_hero_log_data', 'hero_log_id') - op.drop_constraint(None, 'sao_equipment_data', type_='foreignkey') - op.drop_constraint(None, 'sao_equipment_data', type_='foreignkey') - op.drop_constraint(None, 'sao_equipment_data', type_='foreignkey') - op.drop_constraint(None, 'sao_equipment_data', type_='foreignkey') - op.drop_constraint(None, 'sao_equipment_data', type_='foreignkey') + op.drop_constraint("sao_equipment_data_ibfk_2", 'sao_equipment_data', type_='foreignkey') + op.drop_constraint("sao_equipment_data_ibfk_3", 'sao_equipment_data', type_='foreignkey') + op.drop_constraint("sao_equipment_data_ibfk_4", 'sao_equipment_data', type_='foreignkey') + op.drop_constraint("sao_equipment_data_ibfk_5", 'sao_equipment_data', type_='foreignkey') + op.drop_constraint("sao_equipment_data_ibfk_6", 'sao_equipment_data', type_='foreignkey') op.alter_column('sao_equipment_data', 'get_date', existing_type=mysql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), From 40a66614bcfac371fe7447360158c5cbfe5b3582 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Thu, 27 Jun 2024 00:04:23 -0400 Subject: [PATCH 68/76] sao: fix user_hero_log_id --- .../versions/680789dabab3_sao_player_changes.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/core/data/alembic/versions/680789dabab3_sao_player_changes.py b/core/data/alembic/versions/680789dabab3_sao_player_changes.py index 295a53e..4fb953e 100644 --- a/core/data/alembic/versions/680789dabab3_sao_player_changes.py +++ b/core/data/alembic/versions/680789dabab3_sao_player_changes.py @@ -45,7 +45,6 @@ def upgrade(): op.create_foreign_key(None, 'sao_equipment_data', 'sao_static_property', ['property3_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') op.create_foreign_key(None, 'sao_equipment_data', 'sao_static_property', ['property1_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') op.create_foreign_key(None, 'sao_equipment_data', 'sao_static_equipment_list', ['equipment_id'], ['EquipmentId'], onupdate='cascade', ondelete='cascade') - op.add_column('sao_hero_log_data', sa.Column('hero_log_id', sa.BIGINT(), nullable=False)) op.add_column('sao_hero_log_data', sa.Column('max_level_extend_num', sa.INTEGER(), server_default='0', nullable=False)) op.add_column('sao_hero_log_data', sa.Column('is_awakenable', sa.BOOLEAN(), server_default='0', nullable=False)) op.add_column('sao_hero_log_data', sa.Column('awakening_stage', sa.INTEGER(), server_default='0', nullable=False)) @@ -97,6 +96,11 @@ def upgrade(): existing_type=mysql.TIMESTAMP(), server_default=sa.text('now()'), existing_nullable=False) + op.alter_column("sao_hero_log_data", "user_hero_log_id", + existing_type=sa.Integer(), + new_column_name="hero_log_id", + type_=sa.BIGINT(), + nullable=False) op.drop_index('sao_hero_log_data_ibfk_1', 'sao_hero_log_data', type_='foreignkey') op.drop_constraint('sao_hero_log_data_uk', 'sao_hero_log_data', type_='unique') op.create_unique_constraint('sao_hero_log_data_uk', 'sao_hero_log_data', ['user', 'hero_log_id']) @@ -113,7 +117,6 @@ def upgrade(): op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_hero_list', ['hero_log_id'], ['HeroLogId'], onupdate='cascade', ondelete='cascade') op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_property', ['property2_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') op.create_foreign_key(None, "sao_hero_log_data", "aime_user", ['user'], ['id']) - op.drop_column('sao_hero_log_data', 'user_hero_log_id') op.create_foreign_key(None, 'sao_hero_party', 'sao_hero_log_data', ['user_hero_log_id_3'], ['id'], onupdate='cascade', ondelete='cascade') op.create_foreign_key(None, 'sao_hero_party', 'sao_hero_log_data', ['user_hero_log_id_1'], ['id'], onupdate='cascade', ondelete='cascade') op.create_foreign_key(None, 'sao_hero_party', 'sao_hero_log_data', ['user_hero_log_id_2'], ['id'], onupdate='cascade', ondelete='cascade') @@ -185,7 +188,11 @@ def downgrade(): op.drop_constraint("sao_hero_party_ibfk_2", 'sao_hero_party', type_='foreignkey') op.drop_constraint("sao_hero_party_ibfk_3", 'sao_hero_party', type_='foreignkey') op.drop_constraint("sao_hero_party_ibfk_4", 'sao_hero_party', type_='foreignkey') - op.add_column('sao_hero_log_data', sa.Column('user_hero_log_id', mysql.INTEGER(), autoincrement=False, nullable=False)) + op.alter_column("sao_hero_log_data", "hero_log_id", + existing_type=sa.BIGINT(), + new_column_name="user_hero_log_id", + type_=sa.Integer(), + nullable=False) op.drop_constraint("sao_hero_log_data_ibfk_1", 'sao_hero_log_data', type_='foreignkey') op.drop_constraint("sao_hero_log_data_ibfk_2", 'sao_hero_log_data', type_='foreignkey') op.drop_constraint("sao_hero_log_data_ibfk_3", 'sao_hero_log_data', type_='foreignkey') @@ -253,7 +260,6 @@ def downgrade(): op.drop_column('sao_hero_log_data', 'awakening_stage') op.drop_column('sao_hero_log_data', 'is_awakenable') op.drop_column('sao_hero_log_data', 'max_level_extend_num') - op.drop_column('sao_hero_log_data', 'hero_log_id') op.drop_constraint("sao_equipment_data_ibfk_2", 'sao_equipment_data', type_='foreignkey') op.drop_constraint("sao_equipment_data_ibfk_3", 'sao_equipment_data', type_='foreignkey') op.drop_constraint("sao_equipment_data_ibfk_4", 'sao_equipment_data', type_='foreignkey') From 177f8513685b6662c88ca2f8419c25833459da02 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Thu, 27 Jun 2024 00:07:10 -0400 Subject: [PATCH 69/76] sao: cleanup --- .../alembic/versions/680789dabab3_sao_player_changes.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/core/data/alembic/versions/680789dabab3_sao_player_changes.py b/core/data/alembic/versions/680789dabab3_sao_player_changes.py index 4fb953e..d65f725 100644 --- a/core/data/alembic/versions/680789dabab3_sao_player_changes.py +++ b/core/data/alembic/versions/680789dabab3_sao_player_changes.py @@ -101,9 +101,6 @@ def upgrade(): new_column_name="hero_log_id", type_=sa.BIGINT(), nullable=False) - op.drop_index('sao_hero_log_data_ibfk_1', 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint('sao_hero_log_data_uk', 'sao_hero_log_data', type_='unique') - op.create_unique_constraint('sao_hero_log_data_uk', 'sao_hero_log_data', ['user', 'hero_log_id']) op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_property', ['property4_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_skill', ['skill_slot1_skill_id'], ['SkillId'], onupdate='set null', ondelete='set null') op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_skill', ['skill_slot5_skill_id'], ['SkillId'], onupdate='set null', ondelete='set null') @@ -116,7 +113,6 @@ def upgrade(): op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_property', ['property1_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_hero_list', ['hero_log_id'], ['HeroLogId'], onupdate='cascade', ondelete='cascade') op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_property', ['property2_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') - op.create_foreign_key(None, "sao_hero_log_data", "aime_user", ['user'], ['id']) op.create_foreign_key(None, 'sao_hero_party', 'sao_hero_log_data', ['user_hero_log_id_3'], ['id'], onupdate='cascade', ondelete='cascade') op.create_foreign_key(None, 'sao_hero_party', 'sao_hero_log_data', ['user_hero_log_id_1'], ['id'], onupdate='cascade', ondelete='cascade') op.create_foreign_key(None, 'sao_hero_party', 'sao_hero_log_data', ['user_hero_log_id_2'], ['id'], onupdate='cascade', ondelete='cascade') @@ -193,7 +189,6 @@ def downgrade(): new_column_name="user_hero_log_id", type_=sa.Integer(), nullable=False) - op.drop_constraint("sao_hero_log_data_ibfk_1", 'sao_hero_log_data', type_='foreignkey') op.drop_constraint("sao_hero_log_data_ibfk_2", 'sao_hero_log_data', type_='foreignkey') op.drop_constraint("sao_hero_log_data_ibfk_3", 'sao_hero_log_data', type_='foreignkey') op.drop_constraint("sao_hero_log_data_ibfk_4", 'sao_hero_log_data', type_='foreignkey') @@ -206,9 +201,6 @@ def downgrade(): op.drop_constraint("sao_hero_log_data_ibfk_11", 'sao_hero_log_data', type_='foreignkey') op.drop_constraint("sao_hero_log_data_ibfk_12", 'sao_hero_log_data', type_='foreignkey') op.drop_constraint("sao_hero_log_data_ibfk_13", 'sao_hero_log_data', type_='foreignkey') - op.drop_constraint('sao_hero_log_data_uk', 'sao_hero_log_data', type_='unique') - op.create_unique_constraint('sao_hero_log_data_uk', 'sao_hero_log_data', ['user', 'user_hero_log_id']) - op.create_foreign_key(None, "sao_hero_log_data", "aime_user", ['user'], ['id']) op.alter_column('sao_hero_log_data', 'get_date', existing_type=mysql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), From 601cec9075eac61c59b3a9850e7330a3fa4d83fe Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Thu, 27 Jun 2024 10:02:19 -0400 Subject: [PATCH 70/76] sao: you already know what it is --- .../680789dabab3_sao_player_changes.py | 47 +++++++++++-------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/core/data/alembic/versions/680789dabab3_sao_player_changes.py b/core/data/alembic/versions/680789dabab3_sao_player_changes.py index d65f725..9cc60d4 100644 --- a/core/data/alembic/versions/680789dabab3_sao_player_changes.py +++ b/core/data/alembic/versions/680789dabab3_sao_player_changes.py @@ -64,14 +64,6 @@ def upgrade(): op.add_column('sao_hero_log_data', sa.Column('property4_value1', sa.INTEGER(), server_default='0', nullable=False)) op.add_column('sao_hero_log_data', sa.Column('property4_value2', sa.INTEGER(), server_default='0', nullable=False)) op.add_column('sao_hero_log_data', sa.Column('converted_card_num', sa.INTEGER(), server_default='0', nullable=False)) - op.alter_column('sao_hero_log_data', 'main_weapon', - existing_type=mysql.INTEGER(), - type_=sa.BIGINT(), - nullable=True) - op.alter_column('sao_hero_log_data', 'sub_equipment', - existing_type=mysql.INTEGER(), - type_=sa.BIGINT(), - nullable=True) op.alter_column('sao_hero_log_data', 'skill_slot1_skill_id', existing_type=mysql.INTEGER(), type_=sa.BIGINT(), @@ -101,6 +93,24 @@ def upgrade(): new_column_name="hero_log_id", type_=sa.BIGINT(), nullable=False) + op.execute(sa.text("UPDATE sao_hero_log_data SET skill_slot1_skill_id = NULL WHERE skill_slot1_skill_id = 0;")) + op.execute(sa.text("UPDATE sao_hero_log_data SET skill_slot2_skill_id = NULL WHERE skill_slot2_skill_id = 0;")) + op.execute(sa.text("UPDATE sao_hero_log_data SET skill_slot3_skill_id = NULL WHERE skill_slot3_skill_id = 0;")) + op.execute(sa.text("UPDATE sao_hero_log_data SET skill_slot4_skill_id = NULL WHERE skill_slot4_skill_id = 0;")) + op.execute(sa.text("UPDATE sao_hero_log_data SET skill_slot5_skill_id = NULL WHERE skill_slot5_skill_id = 0;")) + op.execute(sa.text("UPDATE sao_hero_log_data SET main_weapon = NULL WHERE main_weapon = 0;")) + op.execute(sa.text("UPDATE sao_hero_log_data SET sub_equipment = NULL WHERE sub_equipment = 0;")) + op.execute(sa.text("UPDATE sao_hero_party SET user_hero_log_id_1 = NULL WHERE user_hero_log_id_1 = 0;")) + op.execute(sa.text("UPDATE sao_hero_party SET user_hero_log_id_2 = NULL WHERE user_hero_log_id_2 = 0;")) + op.execute(sa.text("UPDATE sao_hero_party SET user_hero_log_id_3 = NULL WHERE user_hero_log_id_3 = 0;")) + + op.execute(sa.text("UPDATE sao_hero_log_data INNER JOIN sao_equipment_data ON sao_hero_log_data.main_weapon = sao_equipment_data.equipment_id SET sao_hero_log_data.main_weapon = sao_equipment_data.id;")) + op.execute(sa.text("UPDATE sao_hero_log_data INNER JOIN sao_equipment_data ON sao_hero_log_data.sub_equipment = sao_equipment_data.equipment_id SET sao_hero_log_data.sub_equipment = sao_equipment_data.id;")) + + op.execute(sa.text("UPDATE sao_hero_party INNER JOIN sao_hero_log_data ON sao_hero_party.user_hero_log_id_1 = sao_hero_log_data.hero_log_id SET sao_hero_party.user_hero_log_id_1 = sao_hero_log_data.id;")) + op.execute(sa.text("UPDATE sao_hero_party INNER JOIN sao_hero_log_data ON sao_hero_party.user_hero_log_id_2 = sao_hero_log_data.hero_log_id SET sao_hero_party.user_hero_log_id_2 = sao_hero_log_data.id;")) + op.execute(sa.text("UPDATE sao_hero_party INNER JOIN sao_hero_log_data ON sao_hero_party.user_hero_log_id_3 = sao_hero_log_data.hero_log_id SET sao_hero_party.user_hero_log_id_3 = sao_hero_log_data.id;")) + op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_property', ['property4_property_id'], ['PropertyId'], onupdate='cascade', ondelete='cascade') op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_skill', ['skill_slot1_skill_id'], ['SkillId'], onupdate='set null', ondelete='set null') op.create_foreign_key(None, 'sao_hero_log_data', 'sao_static_skill', ['skill_slot5_skill_id'], ['SkillId'], onupdate='set null', ondelete='set null') @@ -125,17 +135,16 @@ def upgrade(): server_default=sa.text('now()'), existing_nullable=False) op.add_column('sao_player_quest', sa.Column('quest_type', sa.INTEGER(), server_default='1', nullable=False)) - op.add_column('sao_player_quest', sa.Column('quest_scene_id', sa.BIGINT(), nullable=False)) op.alter_column('sao_player_quest', 'play_date', existing_type=mysql.TIMESTAMP(), server_default=sa.text('now()'), existing_nullable=False) - op.drop_index('sao_player_quest_ibfk_1', 'sao_player_quest', type_='foreignkey') - op.drop_constraint('sao_player_quest_uk', 'sao_player_quest', type_='unique') - op.create_unique_constraint('sao_player_quest_uk', 'sao_player_quest', ['user', 'quest_scene_id']) - op.create_foreign_key(None, 'sao_player_quest', 'aime_user', ['user'], ['id'], onupdate='cascade', ondelete='cascade') + op.alter_column('sao_player_quest', 'episode_id', + existing_type=mysql.INTEGER(), + new_column_name="quest_scene_id", + type_=sa.BIGINT(), + nullable=False) op.create_foreign_key(None, 'sao_player_quest', 'sao_static_quest', ['quest_scene_id'], ['QuestSceneId'], onupdate='cascade', ondelete='cascade') - op.drop_column('sao_player_quest', 'episode_id') op.add_column('sao_profile', sa.Column('my_shop', sa.INTEGER(), nullable=True)) op.add_column('sao_profile', sa.Column('fav_hero', sa.INTEGER(), nullable=True)) op.add_column('sao_profile', sa.Column('when_register', sa.TIMESTAMP(), server_default=sa.text('now()'), nullable=True)) @@ -161,12 +170,12 @@ def downgrade(): op.drop_column('sao_profile', 'when_register') op.drop_column('sao_profile', 'fav_hero') op.drop_column('sao_profile', 'my_shop') - op.add_column('sao_player_quest', sa.Column('episode_id', mysql.INTEGER(), autoincrement=False, nullable=False)) - op.drop_constraint("sao_player_quest_ibfk_1", 'sao_player_quest', type_='foreignkey') + op.alter_column('sao_player_quest', 'quest_scene_id', + existing_type=mysql.BIGINT(), + new_column_name="episode_id", + type_=sa.INTEGER(), + nullable=False) op.drop_constraint("sao_player_quest_ibfk_2", 'sao_player_quest', type_='foreignkey') - op.drop_constraint('sao_player_quest_uk', 'sao_player_quest', type_='unique') - op.create_unique_constraint('sao_player_quest_uk', 'sao_player_quest', ['user', 'episode_id']) - op.create_foreign_key(None, 'sao_player_quest', 'aime_user', ['user'], ['id'], onupdate='cascade', ondelete='cascade') op.alter_column('sao_player_quest', 'play_date', existing_type=mysql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), From f0515a21300b19eb472f6381c407ec4b231aa121 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Thu, 27 Jun 2024 23:24:12 -0400 Subject: [PATCH 71/76] sao: fix quest failing to save --- titles/sao/base.py | 8 ++++---- titles/sao/frontend.py | 2 -- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/titles/sao/base.py b/titles/sao/base.py index a589778..44a7801 100644 --- a/titles/sao/base.py +++ b/titles/sao/base.py @@ -1230,7 +1230,7 @@ class SaoBase: if current_data: await self.data.item.put_player_quest( user_id, - QuestType.EPISODE, + int(QuestType.EPISODE), ep_data['QuestSceneId'], False if not quest_clear_flag and not current_data['quest_clear_flag'] else True, min(clear_time, current_data['clear_time']) if quest_clear_flag else current_data['clear_time'], @@ -1241,7 +1241,7 @@ class SaoBase: else: await self.data.item.put_player_quest( user_id, - QuestType.EPISODE, + int(QuestType.EPISODE), ep_data['QuestSceneId'], quest_clear_flag, clear_time, @@ -1458,7 +1458,7 @@ class SaoBase: if current_data: await self.data.item.put_player_quest( user_id, - QuestType.TRIAL_TOWER, + int(QuestType.TRIAL_TOWER), ep_data['QuestSceneId'], False if not quest_clear_flag and not current_data['quest_clear_flag'] else True, min(clear_time, current_data['clear_time']) if quest_clear_flag else current_data['clear_time'], @@ -1469,7 +1469,7 @@ class SaoBase: else: await self.data.item.put_player_quest( user_id, - QuestType.TRIAL_TOWER, + int(QuestType.TRIAL_TOWER), ep_data['QuestSceneId'], quest_clear_flag, clear_time, diff --git a/titles/sao/frontend.py b/titles/sao/frontend.py index 401257f..f9264fc 100644 --- a/titles/sao/frontend.py +++ b/titles/sao/frontend.py @@ -257,8 +257,6 @@ class SaoFrontend(FE_Base): return RedirectResponse("/game/sao/?e=97", 303) await self.data.item.put_hero_party(usr_sesh.user_id, 0, hero1, hero2, hero3) - # await self.data.item.put_player_quest(usr_sesh.user_id, 1001, True, 300, 0, 0, 1) - # Force the tutorial stage to be completed due to potential crash in-game self.logger.info(f"Web registered User {usr_sesh.user_id} profile {profile_id} with name {name}") return RedirectResponse("/game/sao/?s=1", 303) \ No newline at end of file From 7242a187ab4c5b27e14ad703a906dd2ae75db7af Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Fri, 28 Jun 2024 12:25:33 -0400 Subject: [PATCH 72/76] sao: fix sao_hero_log_data nullability --- .../versions/680789dabab3_sao_player_changes.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/core/data/alembic/versions/680789dabab3_sao_player_changes.py b/core/data/alembic/versions/680789dabab3_sao_player_changes.py index 9cc60d4..3e4aef7 100644 --- a/core/data/alembic/versions/680789dabab3_sao_player_changes.py +++ b/core/data/alembic/versions/680789dabab3_sao_player_changes.py @@ -64,6 +64,12 @@ def upgrade(): op.add_column('sao_hero_log_data', sa.Column('property4_value1', sa.INTEGER(), server_default='0', nullable=False)) op.add_column('sao_hero_log_data', sa.Column('property4_value2', sa.INTEGER(), server_default='0', nullable=False)) op.add_column('sao_hero_log_data', sa.Column('converted_card_num', sa.INTEGER(), server_default='0', nullable=False)) + op.alter_column('sao_hero_log_data', 'main_weapon', + existing_type=mysql.INTEGER(), + nullable=True) + op.alter_column('sao_hero_log_data', 'sub_equipment', + existing_type=mysql.INTEGER(), + nullable=True) op.alter_column('sao_hero_log_data', 'skill_slot1_skill_id', existing_type=mysql.INTEGER(), type_=sa.BIGINT(), @@ -235,12 +241,10 @@ def downgrade(): type_=mysql.INTEGER(), nullable=False) op.alter_column('sao_hero_log_data', 'sub_equipment', - existing_type=sa.BIGINT(), - type_=mysql.INTEGER(), + existing_type=mysql.INTEGER(), nullable=False) op.alter_column('sao_hero_log_data', 'main_weapon', - existing_type=sa.BIGINT(), - type_=mysql.INTEGER(), + existing_type=mysql.INTEGER(), nullable=False) op.drop_column('sao_hero_log_data', 'converted_card_num') op.drop_column('sao_hero_log_data', 'property4_value2') From 4446ff1f21071f2aa6b58345bc04fcbec5e65676 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Fri, 28 Jun 2024 15:48:27 -0400 Subject: [PATCH 73/76] mai2: add present support --- .../versions/5ea363686347_mai2_presents.py | 41 ++++++++++++ docs/game_specific_info.md | 8 +++ titles/mai2/base.py | 20 +++++- titles/mai2/dx.py | 37 ++++++++--- titles/mai2/schema/item.py | 66 ++++++++++++++++++- titles/mai2/schema/profile.py | 2 +- 6 files changed, 160 insertions(+), 14 deletions(-) create mode 100644 core/data/alembic/versions/5ea363686347_mai2_presents.py diff --git a/core/data/alembic/versions/5ea363686347_mai2_presents.py b/core/data/alembic/versions/5ea363686347_mai2_presents.py new file mode 100644 index 0000000..8e07a5c --- /dev/null +++ b/core/data/alembic/versions/5ea363686347_mai2_presents.py @@ -0,0 +1,41 @@ +"""mai2_presents + +Revision ID: 5ea363686347 +Revises: 680789dabab3 +Create Date: 2024-06-28 14:49:07.666879 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = '5ea363686347' +down_revision = '680789dabab3' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('mai2_item_present', + sa.Column('id', sa.BIGINT(), nullable=False), + sa.Column('version', sa.INTEGER(), nullable=True), + sa.Column('user', sa.Integer(), nullable=True), + sa.Column('itemKind', sa.INTEGER(), nullable=False), + sa.Column('itemId', sa.INTEGER(), nullable=False), + sa.Column('stock', sa.INTEGER(), server_default='1', nullable=False), + sa.Column('startDate', sa.TIMESTAMP(), nullable=True), + sa.Column('endDate', sa.TIMESTAMP(), nullable=True), + sa.ForeignKeyConstraint(['user'], ['aime_user.id'], onupdate='cascade', ondelete='cascade'), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('version', 'user', 'itemKind', 'itemId', name='mai2_item_present_uk'), + mysql_charset='utf8mb4' + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('mai2_item_present') + # ### end Alembic commands ### diff --git a/docs/game_specific_info.md b/docs/game_specific_info.md index e81a3c6..9fadf80 100644 --- a/docs/game_specific_info.md +++ b/docs/game_specific_info.md @@ -182,6 +182,14 @@ Config file is located in `config/cxb.yaml`. ## maimai DX +### Presents +Presents are items given to the user when they login, with a little animation (for example, the KOP song was given to the finalists as a present). To add a present, you must insert it into the `mai2_item_present` table. In that table, a NULL version means any version, a NULL user means any user, a NULL start date means always open, and a NULL end date means it never expires. Below is a list of presents one might wish to add: + +| Game Version | Item ID | Item Kind | Item Description | Present Description | +|--------------|---------|-----------|-------------------------------------------------|------------------------------------------------| +| BUDDiES (21) | 409505 | Icon (3) | 旅行スタンプ(月面基地) (Travel Stamp - Moon Base) | Officially obtained on the webui with a serial | +| | | | | number, for project raputa | + ### Versions | Game Code | Version ID | Version Name | diff --git a/titles/mai2/base.py b/titles/mai2/base.py index 4d31213..79905ff 100644 --- a/titles/mai2/base.py +++ b/titles/mai2/base.py @@ -471,7 +471,25 @@ class Mai2Base: } async def handle_get_user_present_api_request(self, data: Dict) -> Dict: - return { "userId": data.get("userId", 0), "length": 0, "userPresentList": []} + items: List[Dict[str, Any]] = [] + user_pres_list = await self.data.item.get_presents_by_version_user(self.version, data["userId"]) + if user_pres_list: + for present in user_pres_list: + if (present['startDate'] and present['startDate'].timestamp() > datetime.now().timestamp()): + self.logger.debug(f"Present {present['id']} distribution hasn't started yet (begins {present['startDate']})") + continue # present period hasn't started yet, move onto the next one + + if (present['endDate'] and present['endDate'].timestamp() < datetime.now().timestamp()): + self.logger.warn(f"Present {present['id']} ended on {present['endDate']} and should be removed") + continue # present period ended, move onto the next one + + test = await self.data.item.get_item(data["userId"], present['itemKind'], present['itemId']) + if not test: # Don't send presents for items the user already has + items.append({"itemId": present['itemId'], "itemKind": present['itemKind'], "stock": present['stock'], "isValid": True}) + self.logger.info(f"Give user {data['userId']} {present['stock']}x item {present['itemId']} (kind {present['itemKind']}) as present") + await self.data.item.put_item(data["userId"], present['itemKind'], present['itemId'], present['stock'], True) + + return { "userId": data.get("userId", 0), "length": len(items), "userPresentList": items} async def handle_get_transfer_friend_api_request(self, data: Dict) -> Dict: return {} diff --git a/titles/mai2/dx.py b/titles/mai2/dx.py index 04c9172..5ca03f7 100644 --- a/titles/mai2/dx.py +++ b/titles/mai2/dx.py @@ -327,16 +327,35 @@ class Mai2DX(Mai2Base): async def handle_get_user_item_api_request(self, data: Dict) -> Dict: kind = int(data["nextIndex"] / 10000000000) next_idx = int(data["nextIndex"] % 10000000000) - user_item_list = await self.data.item.get_items(data["userId"], kind) - 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 + + if kind == 4: # presents + user_pres_list = await self.data.item.get_presents_by_version_user(self.version, data["userId"]) + if user_pres_list: + for present in user_pres_list: + if (present['startDate'] and present['startDate'].timestamp() > datetime.now().timestamp()): + self.logger.debug(f"Present {present['id']} distribution hasn't started yet (begins {present['startDate']})") + continue # present period hasn't started yet, move onto the next one + + if (present['endDate'] and present['endDate'].timestamp() < datetime.now().timestamp()): + self.logger.warn(f"Present {present['id']} ended on {present['endDate']} and should be removed") + continue # present period ended, move onto the next one + + test = await self.data.item.get_item(data["userId"], present['itemKind'], present['itemId']) + if not test: # Don't send presents for items the user already has + items.append({"itemId": present['itemId'], "itemKind": present['itemKind'], "stock": present['stock'], "isValid": True}) + self.logger.info(f"Give user {data['userId']} {present['stock']}x item {present['itemId']} (kind {present['itemKind']}) as present") + await self.data.item.put_item(data["userId"], present['itemKind'], present['itemId'], present['stock'], True) + + else: + user_item_list = await self.data.item.get_items(data["userId"], kind) + 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) diff --git a/titles/mai2/schema/item.py b/titles/mai2/schema/item.py index f22cccd..65e129f 100644 --- a/titles/mai2/schema/item.py +++ b/titles/mai2/schema/item.py @@ -2,8 +2,8 @@ from core.data.schema import BaseData, metadata from datetime import datetime 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 import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_, or_ +from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, BIGINT, INTEGER from sqlalchemy.schema import ForeignKey from sqlalchemy.sql import func, select from sqlalchemy.dialects.mysql import insert @@ -198,6 +198,20 @@ print_detail = Table( mysql_charset="utf8mb4", ) +present = Table( + "mai2_item_present", + metadata, + Column('id', BIGINT, primary_key=True, nullable=False), + Column('version', INTEGER), + Column("user", Integer, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")), + Column("itemKind", INTEGER, nullable=False), + Column("itemId", INTEGER, nullable=False), + Column("stock", INTEGER, nullable=False, server_default="1"), + Column("startDate", TIMESTAMP), + Column("endDate", TIMESTAMP), + UniqueConstraint("version", "user", "itemKind", "itemId", name="mai2_item_present_uk"), + mysql_charset="utf8mb4", +) class Mai2ItemData(BaseData): async def put_item( @@ -476,7 +490,7 @@ class Mai2ItemData(BaseData): musicId = music_id ) - conflict = sql.on_duplicate_key_do_nothing() + conflict = sql.on_duplicate_key_update(musicId = music_id) result = await self.execute(conflict) if result: @@ -586,3 +600,49 @@ class Mai2ItemData(BaseData): ) return None return result.lastrowid + + async def put_present(self, item_kind: int, item_id: int, version: int = None, user_id: int = None, start_date: datetime = None, end_date: datetime = None) -> Optional[int]: + sql = insert(present).values( + version = version, + user = user_id, + itemKind = item_kind, + itemId = item_id, + startDate = start_date, + endDate = end_date + ) + + conflict = sql.on_duplicate_key_update( + startDate = start_date, + endDate = end_date + ) + + result = await self.execute(conflict) + if result: + return result.lastrowid + + self.logger.error(f"Failed to add present item {item_id}!") + + async def get_presents_by_user(self, user_id: int = None) -> Optional[List[Row]]: + result = await self.execute(present.select(or_(present.c.user == user_id, present.c.user is None))) + if result: + return result.fetchall() + + async def get_presents_by_version(self, ver: int = None) -> Optional[List[Row]]: + result = await self.execute(present.select(or_(present.c.version == ver, present.c.version is None))) + if result: + return result.fetchall() + + async def get_presents_by_version_user(self, ver: int = None, user_id: int = None) -> Optional[List[Row]]: + result = await self.execute(present.select( + and_( + or_(present.c.user == user_id, present.c.user is None)), + or_(present.c.version == ver, present.c.version is None) + ) + ) + if result: + return result.fetchall() + + async def get_present_by_id(self, present_id: int) -> Optional[Row]: + result = await self.execute(present.select(present.c.id == present_id)) + if result: + return result.fetchone() diff --git a/titles/mai2/schema/profile.py b/titles/mai2/schema/profile.py index 1b76b07..c191a1a 100644 --- a/titles/mai2/schema/profile.py +++ b/titles/mai2/schema/profile.py @@ -892,7 +892,7 @@ class Mai2ProfileData(BaseData): rival = rival_id ) - conflict = sql.on_duplicate_key_do_nothing() + conflict = sql.on_duplicate_key_update(rival = rival_id) result = await self.execute(conflict) if result: From e9afaaf56cd42fd1c8f823d41c728d1375ac4896 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Fri, 28 Jun 2024 15:51:28 -0400 Subject: [PATCH 74/76] update changelog --- changelog.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/changelog.md b/changelog.md index f9c6563..17fef90 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,14 @@ # Changelog Documenting updates to ARTEMiS, to be updated every time the master branch is pushed to. +## 20240628 +### maimai ++ Add present support + +## 20240627 +### SAO ++ Fix ghost items, character and player XP, EX Bonuses, unlocks, and much much more + ## 20240620 ### CHUNITHM + CHUNITHM LUMINOUS support From af8bd1d1c08be6f9e2fc50987cf0934b29091c65 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Fri, 28 Jun 2024 23:29:31 -0400 Subject: [PATCH 75/76] mai2: fix presents --- titles/mai2/base.py | 5 ++++- titles/mai2/dx.py | 5 ++++- titles/mai2/schema/item.py | 6 +++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/titles/mai2/base.py b/titles/mai2/base.py index 79905ff..16b2039 100644 --- a/titles/mai2/base.py +++ b/titles/mai2/base.py @@ -474,6 +474,7 @@ class Mai2Base: items: List[Dict[str, Any]] = [] user_pres_list = await self.data.item.get_presents_by_version_user(self.version, data["userId"]) if user_pres_list: + self.logger.debug(f"Found {len(user_pres_list)} possible presents") for present in user_pres_list: if (present['startDate'] and present['startDate'].timestamp() > datetime.now().timestamp()): self.logger.debug(f"Present {present['id']} distribution hasn't started yet (begins {present['startDate']})") @@ -485,7 +486,9 @@ class Mai2Base: test = await self.data.item.get_item(data["userId"], present['itemKind'], present['itemId']) if not test: # Don't send presents for items the user already has - items.append({"itemId": present['itemId'], "itemKind": present['itemKind'], "stock": present['stock'], "isValid": True}) + pres_id = present['itemKind'] * 1000000 + pres_id += present['itemId'] + items.append({"itemId": pres_id, "itemKind": 4, "stock": present['stock'], "isValid": True}) self.logger.info(f"Give user {data['userId']} {present['stock']}x item {present['itemId']} (kind {present['itemKind']}) as present") await self.data.item.put_item(data["userId"], present['itemKind'], present['itemId'], present['stock'], True) diff --git a/titles/mai2/dx.py b/titles/mai2/dx.py index 5ca03f7..3b28f30 100644 --- a/titles/mai2/dx.py +++ b/titles/mai2/dx.py @@ -332,6 +332,7 @@ class Mai2DX(Mai2Base): if kind == 4: # presents user_pres_list = await self.data.item.get_presents_by_version_user(self.version, data["userId"]) if user_pres_list: + self.logger.debug(f"Found {len(user_pres_list)} possible presents") for present in user_pres_list: if (present['startDate'] and present['startDate'].timestamp() > datetime.now().timestamp()): self.logger.debug(f"Present {present['id']} distribution hasn't started yet (begins {present['startDate']})") @@ -343,7 +344,9 @@ class Mai2DX(Mai2Base): test = await self.data.item.get_item(data["userId"], present['itemKind'], present['itemId']) if not test: # Don't send presents for items the user already has - items.append({"itemId": present['itemId'], "itemKind": present['itemKind'], "stock": present['stock'], "isValid": True}) + pres_id = present['itemKind'] * 1000000 + pres_id += present['itemId'] + items.append({"itemId": pres_id, "itemKind": 4, "stock": present['stock'], "isValid": True}) self.logger.info(f"Give user {data['userId']} {present['stock']}x item {present['itemId']} (kind {present['itemKind']}) as present") await self.data.item.put_item(data["userId"], present['itemKind'], present['itemId'], present['stock'], True) diff --git a/titles/mai2/schema/item.py b/titles/mai2/schema/item.py index 65e129f..d53ebbc 100644 --- a/titles/mai2/schema/item.py +++ b/titles/mai2/schema/item.py @@ -635,10 +635,10 @@ class Mai2ItemData(BaseData): async def get_presents_by_version_user(self, ver: int = None, user_id: int = None) -> Optional[List[Row]]: result = await self.execute(present.select( and_( - or_(present.c.user == user_id, present.c.user is None)), - or_(present.c.version == ver, present.c.version is None) + or_(present.c.user == user_id, present.c.user == None), + or_(present.c.version == ver, present.c.version == None) ) - ) + )) if result: return result.fetchall() From ecb2e9ec75035f0733e59491f9939980b1a7d203 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Fri, 28 Jun 2024 23:55:23 -0400 Subject: [PATCH 76/76] mai2: properly add present items --- titles/mai2/base.py | 1 - titles/mai2/dx.py | 16 +++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/titles/mai2/base.py b/titles/mai2/base.py index 16b2039..b3c6a1d 100644 --- a/titles/mai2/base.py +++ b/titles/mai2/base.py @@ -490,7 +490,6 @@ class Mai2Base: pres_id += present['itemId'] items.append({"itemId": pres_id, "itemKind": 4, "stock": present['stock'], "isValid": True}) self.logger.info(f"Give user {data['userId']} {present['stock']}x item {present['itemId']} (kind {present['itemKind']}) as present") - await self.data.item.put_item(data["userId"], present['itemKind'], present['itemId'], present['stock'], True) return { "userId": data.get("userId", 0), "length": len(items), "userPresentList": items} diff --git a/titles/mai2/dx.py b/titles/mai2/dx.py index 3b28f30..7a067d7 100644 --- a/titles/mai2/dx.py +++ b/titles/mai2/dx.py @@ -196,10 +196,17 @@ class Mai2DX(Mai2Base): if "userItemList" in upsert and len(upsert["userItemList"]) > 0: for item in upsert["userItemList"]: + if item["itemKind"] == 4: + item_id = item["itemId"] % 1000000 + item_kind = item["itemId"] // 1000000 + else: + item_id = item["itemId"] + item_kind = item["itemKind"] + await self.data.item.put_item( user_id, - int(item["itemKind"]), - item["itemId"], + item_kind, + item_id, item["stock"], item["isValid"], ) @@ -325,8 +332,8 @@ class Mai2DX(Mai2Base): } async def handle_get_user_item_api_request(self, data: Dict) -> Dict: - kind = int(data["nextIndex"] / 10000000000) - next_idx = int(data["nextIndex"] % 10000000000) + kind = data["nextIndex"] // 10000000000 + next_idx = data["nextIndex"] % 10000000000 items: List[Dict[str, Any]] = [] if kind == 4: # presents @@ -348,7 +355,6 @@ class Mai2DX(Mai2Base): pres_id += present['itemId'] items.append({"itemId": pres_id, "itemKind": 4, "stock": present['stock'], "isValid": True}) self.logger.info(f"Give user {data['userId']} {present['stock']}x item {present['itemId']} (kind {present['itemKind']}) as present") - await self.data.item.put_item(data["userId"], present['itemKind'], present['itemId'], present['stock'], True) else: user_item_list = await self.data.item.get_items(data["userId"], kind)

      FQ~0-pLHe8cXAWXN>e%jtzKb;(3e>c=kFrXZi1G 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%0Ax+`b{~L$0w5pr!rndskdJv8sHE<2F(Ltwk9m1S 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 M*^-X+FdT~i0gl5fEdT%j diff --git a/titles/sao/data/Title.csv b/titles/sao/data/Title.csv deleted file mode 100644 index 73b4dfd7207ab28989dbe5cbc7d5fc46a10a30c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16225 zcmb7LU2hymc6|?!|6uSb0ce0V-?Pt70zV`Oj3loMVVB7ow&h*f#$w;5dn|ra6e)@N zpgt^%6e&ugNb8GQQR0tjcF)X{|3c2W)qSU{XS#-l0I5aRsXF)6t$XWMb^q6w-+w*( z`5&i0`|`U#e|_O^|8e1)+37E4|MJ6^muA12z4-m~|6KU`hgoC)sZ4)y;o?`*pMP`V z|7QR7%dcnu^TPLkn*PUsFw;~2eetV{-~RPtfB!Uj@IJowaB6x+{@_=i{mZ}o-b}IC z%*+h_A9b%q;X%}0j=C?S@NjC{OdB??q)pi>YmT2q-3L*3G3q{zy6>azmOON#?g?>q z;;KVjCBxxoQFkp0x1(?`3g1QDg)wOG0lYSZS2H+%7KJ}Y-EP!f$54WOB6Pt*My?39 zKE&2CEPe*7qV5xp4hzX0#P5?vc$&hiMR=WXbV`Bx&#pwlL43F#@4Wo=1Qzc{;maxL z1Hz^?v{;&FI4ITMyOqqn0W^8`Q*Uve2sAM@7avC z#5iJL+`nJ=?)%wGpM3kjpL{X#8HS7> zZ}xuqIOHOd)uDQFlk4=c4ct zfwh7S%}l5k$j$BaZmdwNotM3x`?h&uoL1Jp4Ifx|90hZO)vb8$O?@vFS&oS z7d(xRj;KSkI6~$MSjAKEjXA8fsCzT&J`-M`CT`X$jF9<+fltJXTm8#7@)~lSR&kun z8xSX~8|{k|z?m4)CEhK`m_HKUE)1CY1Y+4#@AVsat8L}Fg2`L~VPY!&a74dZdJr$Y zN`=B?JIlpnUQmG-3{HMR`=on0{^S_F<-4VfHK8>{l@R{oC)7 z`$!<`QMjBuUx^a=j0^N^=g&hk~LqCiXnfWL+-F%9qHZqy?0rL3*(A44&M7 zcr89g)U%OADYCIpY*~uwHVnA)-jbOvBI)#SteJygWKD({M%rbqb3bF3Li)!QDnx? z_Gt`UM1cogSQ=+2;dV6&ALm?A+%nzc$%q7}8ytd#9EixmiC4RWd1gaN0^!+lhj}1a zjzXk)=$Mz%VmP!sf$?B#FTNi};Y#lT+8``jQ52c63~?lsJ^%iX3w za%cV)`iin<8XN;n`I=f4-MfTsM&z(nx*ArB?79=lyuCNKlf_d+bT9L& zByQ;f%)MQdnov~}sxZOVhsTzxieIDn8^175@6R*uE_LF!yIw15YDJCuj|W@+buK!0 z3Tn!~#wi$!_Asw0^SaE!$0)cSaT~ju#%AsZ5aJ~{hkO5omHf}Lh>Udxj481L(^ywK z>M|RXyXx4k>McZ5*Q&Z&RhLS|*HHui2D=F1kJMGdv60gFa~ z8UMC*?%Z;2DE|idk06I^8#WT;*fx}HlQY8N?75^eBsA>#A4)) z!U_QUBpxHg+q-8U?&5?24(78$^R$t2-`h#KEt;$ileL$M&mv9PhAEq~5bVjp8qO?a zUwrP^iw-h%nZwW1Rk?MV)D4sRIr{S1s9E0FEdM$R9*CJ+@p}Zcr6_ppn=j4qrYd%g zhnL%;+1=ReJ{IreubJI2v-frbuJg5z*mV17Yf3jv=>=3dTg~OzFBM@vlA6SgP2x|Z z;1<_Ka67rXpWNH=QPr$%Y}OuwEhnsL+SoKb3ii>~ylj}4v4oS?Zy_&E?kMu>IUj9J z&4#IYEVi7sW@p3fJPz!mtqIyNLC4!agYBSmoa}t?5!R$^n3M~sa;ln$4HK~o^O4l# zYnXiF)72<=6K`+EJ2!nKHPsrX+5)Paq$XCw#OlI)BsGm1rqRLfLjUz4i>G7h)bJlX=3hUkhU&=Pnl zbD$JYk;mgb{41J3D@>qRU+~-h`xP7}`ek-63V;6^#jYqL5uB9iU zMq2B5C|QO$?uKv=#Pce0L|vU7l?h5tqvZ*k{2U7IkEiGtDYf{4pY#z%3I*Pu10m0t!H|WUZyLr=yLj` z>kM|vW|PICPH5g=aqsB(Yr4dU^XiIgN5@}>i;I^hLqi>Z1xMU<4;P2MJfY|C_9O=? zxGS#R>UmY|4T|6g`6vJ@ z9aYnvEypWY6&xK^tLm&(ud4Nes#W#X>LgWp&*Z3DRbS;6d{S)1T~1z|bSDlgxf36{ z0T)T$R~rRY)2%phUSDk#R86-&dpb8u}Gj-R{-6Fl~#)cmJ3UojSF+et@X?07WUt3eqHmz+5^k@s3j<#*pw(Zx}6tr!t zw(W`9I`7{x8niQQ&1T*Ke}}Yy$n-R$OhfgKGjk+Ja_Y zTeMB%tl^7*L!QNmfYQeCTQpbTL~Wfnj-aj4dMiC-_eWZkM~=2yaHXf~WrYE7PE0`a zjI+Pu!f&0Jfb(|+db9;iM_VnqjKAc|~ zR9={MM_Vnu>@9;Mk(0|{X}Ui^zMdF{(XW|he`8nQsmyoad<~trw}7h<89p& zZ=FhvD9vTiJh!w*a+y<#5vaMWJ~1Y^wHU!R-tv7+Y>|??3%H!`?He#S67`4qUQ#V5#o&W=9mKfW`V+tB`Cxa`uT7wr!_ ziFkXB*IkS6;)NZhmwY(D+u=O;4)>FV7iGlkY_Lnr#yLCk5wr8e{y?{2Xv!qm@8hF{ zw|B}Y*%@OOn6Zgn_{a>W53i*cdg?D7^%w5rbAsg72|gK#f)(B+mrS3$pw4&xX7Gmk zIXm(ZlQ-F=KYrEV`W0SjjzV1I<4qluZm06f4qhD$%DBl}?(7RS#Bb=!L(hH%AzhBT zz5Sqseg&rr%N#!4$C1^j!;g01BV!XJdxEPMwCX*<3nGu`52Ek$YLjJXqGH&Ydi0;3wJ0D4Petwftuvo74AsR0DKVl)A z9dGi5w(-ty)cN^&#yLCk5mV>qcN~TO&Q8|(hB2-cpU(H5t(Q?!pXa9?6T9$`v7da< z)p?;%=qb7VN?zhUh_7yyFiCf~#E<$_0q>)s9>tA)fku3=C~sNu8^B~?9gzwjMwIcf zdnNK2v+hbd@)6V3$Gv!w*t_E>zy%~V&9lJ8> z*uC9b@e5u{_paX>gvVuk)TJwv;(XV-rRG2qJ%Ki!7**l2eonZBsFiJ}rb}{Noqg{AJN^2bUG`h&*7x19- zxOQ6bu$R%LHntZZC0%f=g9`e@himaiWR(Mm)7v;K(?`QLE24>A_{gY7S4TZM`veQ0 zfh@;cj~EHUYh{$wudAbeUDlD0n0j}0)Vuqy7C;G((7Ump+X*@sWlvo`*z!y`}Y3_8XUOP diff --git a/titles/sao/data/YuiMedalShopItems.csv b/titles/sao/data/YuiMedalShopItems.csv deleted file mode 100644 index f5d91fe049abf8002ed086280ccf974c08008f5b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41168 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 diff --git a/titles/sao/frontend.py b/titles/sao/frontend.py new file mode 100644 index 0000000..401257f --- /dev/null +++ b/titles/sao/frontend.py @@ -0,0 +1,264 @@ +import yaml +import jinja2 +from typing import List +from starlette.requests import Request +from starlette.responses import Response, RedirectResponse, JSONResponse, PlainTextResponse +from starlette.routing import Route +from os import path +import random +from Crypto.Protocol.KDF import PBKDF2 +from Crypto.Hash import SHA1 +from Crypto.Cipher import AES, _mode_cbc + +from core.frontend import FE_Base, UserSession +from core.config import CoreConfig +from .database import SaoData +from .config import SaoConfig +from .const import SaoConstants + + +class SaoFrontend(FE_Base): + SN_PREFIX = SaoConstants.SERIAL_IDENT + NETID_PREFIX = SaoConstants.NETID_PREFIX + ALL_HEROS = [] + def __init__( + self, cfg: CoreConfig, environment: jinja2.Environment, cfg_dir: str + ) -> None: + super().__init__(cfg, environment) + self.data = SaoData(cfg) + self.game_cfg = SaoConfig() + if path.exists(f"{cfg_dir}/{SaoConstants.CONFIG_NAME}"): + self.game_cfg.update( + yaml.safe_load(open(f"{cfg_dir}/{SaoConstants.CONFIG_NAME}")) + ) + self.nav_name = "SAO" + self.card_key= None + self.card_iv = None + + if self.game_cfg.card.enable and self.game_cfg.card.crypt_password and self.game_cfg.card.crypt_salt: + hash = PBKDF2( + self.game_cfg.card.crypt_password, + bytes.fromhex(self.game_cfg.card.crypt_salt), + 48, + count=1000, + hmac_hash_module=SHA1, + ) + + self.card_key = hash[:32] + self.card_iv = hash[32:48] + + def get_routes(self) -> List[Route]: + return [ + Route("/", self.render_GET, methods=['GET']), + Route("/update.name", self.change_name, methods=['POST']), + Route("/matching.auth", self.matching_auth, methods=['POST']), + Route("/matching.auth/", self.matching_auth, methods=['POST']), + Route("/qr.read", self.read_qr, methods=['POST']), + Route("/qr.register", self.reg_qr, methods=['POST']), + Route("/profile.register", self.reg_profile, methods=['POST']) + ] + + async def render_GET(self, request: Request) -> Response: + template = self.environment.get_template( + "titles/sao/templates/sao_index.jinja" + ) + pf = None + + usr_sesh = self.validate_session(request) + if not usr_sesh: + usr_sesh = UserSession() + + else: + profile = await self.data.profile.get_profile(usr_sesh.user_id) + if profile is not None: + pf = profile._asdict() + if not self.ALL_HEROS: + self.ALL_HEROS = await self.data.static.get_heros() + + err = 0 + suc = 0 + if "e" in request.query_params: + err = request.query_params.get("e", 0) + if "s" in request.query_params: + suc = request.query_params.get("s", 0) + + return Response(template.render( + title=f"{self.core_config.server.name} | {self.nav_name}", + game_list=self.environment.globals["game_list"], + sesh=vars(usr_sesh), + profile=pf, + success=int(suc), + error=int(err), + all_heros=self.ALL_HEROS if self.ALL_HEROS else [] + ), media_type="text/html; charset=utf-8") + + async def change_name(self, request: Request) -> RedirectResponse: + usr_sesh = self.validate_session(request) + if not usr_sesh: + return RedirectResponse("/game/sao/", 303) + + frm = await request.form() + new_name = frm.get("new_name") + + if len(new_name) > 16: + return RedirectResponse("/game/sao/?e=8", 303) + + await self.data.profile.set_profile_name(usr_sesh.user_id, new_name) + self.logger.info(f"User {usr_sesh.user_id} changed name to {new_name}") + + return RedirectResponse("/game/sao/?s=1", 303) + + async def matching_auth(self, request: Request) -> JSONResponse: + self.logger.debug(f"Mathing auth params: {request.query_params}") + self.logger.debug(f"Mathing auth headers: {request.headers}") + uid = request.query_params.get('userId', '') + + if not uid: + uid = f'Guest{str(random.randint(1,9999)).zfill(4)}' + self.logger.info(f"Matching auth request with no userId, using {uid}") + + else: + self.logger.info(f"Matching auth request for userId {uid}") + + return JSONResponse({ "ResultCode": 1, "UserId": uid }) # Just auth everything for now + + async def read_qr(self, request: Request) -> PlainTextResponse: + if not self.card_key or not self.card_iv: + return PlainTextResponse("e13-1", 400) + + usr_sesh = self.validate_session(request) + if not usr_sesh: + return PlainTextResponse("e9", 403) + + frm = await request.form() + qr_data = frm.get("qr_data", "") + if not qr_data.isalnum() or not len(qr_data) == 0x40: + return PlainTextResponse("e14-1", 400) + + try: + cipher: _mode_cbc.CbcMode = AES.new(self.card_key, AES.MODE_CBC, iv=self.card_iv) + except Exception as e: + self.logger.error(f"Error creating card cipher - {e}") + return PlainTextResponse("e13-2", 500) + sn = b"" + try: + sn = cipher.decrypt(bytes.fromhex(qr_data))[:19] + sn = sn.decode() + except Exception as e: + self.logger.error(f"Error decrypting card data {qr_data} ({sn}) - {e}") + return PlainTextResponse("e14-2", 400) + + if not sn.isnumeric(): + self.logger.error(f"Card serial {sn} decrypted incorrectly") + return PlainTextResponse("e13-3", 400) + + return PlainTextResponse(sn) + + async def reg_qr(self, request: Request) -> RedirectResponse: + if not self.card_key or not self.card_iv: + return RedirectResponse("/game/sao/?e=14", 303) + + usr_sesh = self.validate_session(request) + if not usr_sesh: + return RedirectResponse("/game/sao/?e=9", 303) + + frm = await request.form() + serial = frm.get("qr_register_serial") + hero = frm.get("qr_register_hero") + is_holo = bool(frm.get("qr_register_holo", False)) + + user_hero = await self.data.item.get_hero_log(usr_sesh.user_id, hero) + if not user_hero: + hero_statc = await self.data.static.get_hero_by_id(hero) + if not hero_statc: + self.logger.error(f"Failed to find hero log {hero}! Please run the reader") + return RedirectResponse(" /game/sao/?e=13", 303) + + skills = await self.data.static.get_skill_table_by_subid(hero_statc['SkillTableSubId']) + if not skills: + self.logger.error(f"Failed to find skill table {hero_statc['SkillTableSubId']}! Please run the reader") + return RedirectResponse("/game/sao/?e=13", 303) + + default_skills = [] + now_have_skills = [None, None, None, None, None] + x = 0 + for skill in skills: + if skill['LevelObtained'] == 1 and skill['AwakeningId'] == 0: + default_skills[x] = skill['SkillId'] + x += 1 + if x >= 5: + break + + for skill in default_skills: + skill_info = await self.data.static.get_skill_by_id(skill) + skill_slot = skill_info['Level'] - 1 + if now_have_skills[skill_slot] is not None: + now_have_skills[skill] + + + user_hero_id = await self.data.item.put_hero_log( + usr_sesh.user_id, + hero, + 1, + 0, + hero_statc['DefaultEquipmentId1'], + hero_statc['DefaultEquipmentId2'], + now_have_skills[0], + now_have_skills[1], + now_have_skills[2], + now_have_skills[3], + now_have_skills[4] + ) + if not user_hero_id: + self.logger.error(f"Failed to give user {usr_sesh.user_id} hero {hero}!") + return RedirectResponse("/game/sao/?e=99", 303) + else: + user_hero_id = user_hero['id'] + + card_id = await self.data.profile.put_hero_card(usr_sesh.user_id, serial, user_hero_id, is_holo) + if not card_id: + self.logger.error(f"Failed to give user {usr_sesh.user_id} hero card {hero}!") + return RedirectResponse("/game/sao/?e=99", 303) + + self.logger.info(f"User {usr_sesh.user_id} added hero {hero} as card with id {card_id}") + + return RedirectResponse("/game/sao/?s=1", 303) + + async def reg_profile(self, request: Request) -> RedirectResponse: + usr_sesh = self.validate_session(request) + if not usr_sesh: + return RedirectResponse("/game/sao/?e=9", 303) + + frm = await request.form() + name = frm.get("sao_register_username") + + if len(name) > 16: + return RedirectResponse("/game/sao/?e=8", 303) + + profile_id = await self.data.profile.create_profile(usr_sesh.user_id) + if not profile_id: + self.logger.error(f"Failed to web register User {usr_sesh.user_id} with name {name}") + return RedirectResponse("/game/sao/?e=99", 303) + + await self.data.profile.set_profile_name(usr_sesh.user_id, name) + + equip1 = await self.data.item.put_equipment(usr_sesh.user_id, 101000000) + equip2 = await self.data.item.put_equipment(usr_sesh.user_id, 102000000) + equip3 = await self.data.item.put_equipment(usr_sesh.user_id, 109000000) + if not equip1 or not equip2 or not equip3: + self.logger.error(f"Failed to create profile for user {usr_sesh.user_id} from (could not add equipment)") + return RedirectResponse("/game/sao/?e=98", 303) + + hero1 = await self.data.item.put_hero_log(usr_sesh.user_id, 101000010, 1, 0, equip1, None, 1002, 1003, 1014, None, None) + hero2 = await self.data.item.put_hero_log(usr_sesh.user_id, 102000010, 1, 0, equip2, None, 3001, 3002, 3004, None, None) + hero3 = await self.data.item.put_hero_log(usr_sesh.user_id, 105000010, 1, 0, equip3, None, 10005, 10002, 10004, None, None) + if not hero1 or not hero2 or not hero3: + self.logger.error(f"Failed to create profile for user {usr_sesh.user_id} (could not add heros)") + return RedirectResponse("/game/sao/?e=97", 303) + + await self.data.item.put_hero_party(usr_sesh.user_id, 0, hero1, hero2, hero3) + # await self.data.item.put_player_quest(usr_sesh.user_id, 1001, True, 300, 0, 0, 1) + # Force the tutorial stage to be completed due to potential crash in-game + self.logger.info(f"Web registered User {usr_sesh.user_id} profile {profile_id} with name {name}") + + return RedirectResponse("/game/sao/?s=1", 303) \ No newline at end of file diff --git a/titles/sao/handlers/base.py b/titles/sao/handlers/base.py index 2f2f13e..e2cb9d1 100644 --- a/titles/sao/handlers/base.py +++ b/titles/sao/handlers/base.py @@ -1,10 +1,9 @@ import struct from datetime import datetime -from typing import List -from construct import * +from typing import List, Union from .helpers import * -import csv -from csv import * + +from ..const import GameconnectCmd class SaoRequestHeader: def __init__(self, data: bytes) -> None: @@ -21,10 +20,9 @@ class SaoRequestHeader: class SaoBaseRequest: def __init__(self, header: SaoRequestHeader, data: bytes) -> None: self.header = header - # TODO: Length check class SaoResponseHeader: - def __init__(self, cmd_id: int) -> None: + def __init__(self, cmd_id: GameconnectCmd) -> None: self.cmd = cmd_id self.err_status = 0 self.error_type = 0 @@ -37,12 +35,40 @@ class SaoResponseHeader: return struct.pack("!HHIIIII", self.cmd, self.err_status, self.error_type, self.vendor_id, self.game_id, self.version_id, self.length) class SaoBaseResponse: - def __init__(self, cmd_id: int) -> None: - self.header = SaoResponseHeader(cmd_id) + def __init__(self, cmd_id: Union[GameconnectCmd, int]) -> None: + if type(cmd_id) == int: + cmd = GameconnectCmd(cmd_id) + else: + cmd = cmd_id + self.header = SaoResponseHeader(cmd) def make(self) -> bytes: return self.header.make() +class SaoGenericUserTicketRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + ticket_id = decode_str(data, off) + self.ticket_id = ticket_id[0] + off += ticket_id[1] + + user_id = decode_str(data, off) + self.user_id = user_id[0] + off += user_id[1] + +class SaoGenericUserRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.user_id, new_off = decode_str(data, off) + off += new_off + +class SaoNoopRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + self.dummy = decode_byte(data, 0) + class SaoNoopResponse(SaoBaseResponse): def __init__(self, cmd: int) -> None: super().__init__(cmd) @@ -51,154 +77,164 @@ class SaoNoopResponse(SaoBaseResponse): def make(self) -> bytes: return super().make() + struct.pack("!bI", self.result, 0) - -class SaoGetMaintRequest(SaoBaseRequest): + +class SaoTicketRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: super().__init__(header, data) - # TODO: The rest of the mait info request + off = 0 + self.user_id, new_off = decode_str(data, off) + off += new_off -class SaoGetMaintResponse(SaoBaseResponse): - def __init__(self, cmd) -> None: - super().__init__(cmd) - self.result = 1 - self.maint_begin = datetime.fromtimestamp(0) - self.maint_begin_int_ct = 6 - self.maint_end = datetime.fromtimestamp(0) - self.maint_end_int_ct = 6 - self.dt_format = "%Y%m%d%H%M%S" +class SaoTicketResponse(SaoBaseResponse): + def __init__(self, ticket_id: str = "9") -> None: + super().__init__(GameconnectCmd.TICKET_RESPONSE) + self.result = "1" + self.ticket_id = ticket_id def make(self) -> bytes: - maint_begin_list = [x for x in datetime.strftime(self.maint_begin, self.dt_format)] - maint_end_list = [x for x in datetime.strftime(self.maint_end, self.dt_format)] - self.maint_begin_int_ct = len(maint_begin_list) * 2 - self.maint_end_int_ct = len(maint_end_list) * 2 + return super().make() \ + + encode_str(self.result) \ + + encode_str(self.ticket_id) - maint_begin_bytes = b"" - maint_end_bytes = b"" - - for x in maint_begin_list: - maint_begin_bytes += struct.pack(" None: + super().__init__(GameconnectCmd.GET_MAINTENANCE_INFO_RESPONSE) + self.result = 1 + self.maint_begin = maint_start + self.maint_end = maint_end + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_date_str(self.maint_begin) \ + + encode_date_str(self.maint_end) class SaoCommonAcCabinetBootNotificationRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: super().__init__(header, data) + off = 0 + self.cabinet_type = decode_byte(data, off) + off += BYTE_OFF -class SaoCommonAcCabinetBootNotificationResponse(SaoBaseResponse): - def __init__(self, cmd) -> None: - super().__init__(cmd) - self.result = 1 - - def make(self) -> bytes: - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - ) + self.net_id, new_off = decode_str(data, off) + off += new_off - resp_data = resp_struct.build(dict( - result=self.result, - )) + self.place_id, new_off = decode_str(data, off) + off += new_off - self.length = len(resp_data) - return super().make() + resp_data + self.store_id, new_off = decode_str(data, off) + off += new_off + + self.store_name, new_off = decode_str(data, off) + off += new_off + + self.serial_no, new_off = decode_str(data, off) + off += new_off + + self.current_version_app_id = decode_int(data, off) + off += INT_OFF + + self.current_master_data_version = decode_int(data, off) + off += INT_OFF class SaoMasterDataVersionCheckRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: super().__init__(header, data) + self.current_data_version = decode_int(data, 0) class SaoMasterDataVersionCheckResponse(SaoBaseResponse): - def __init__(self, cmd) -> None: - super().__init__(cmd) + def __init__(self, current_ver: int, game_ver: int) -> None: + super().__init__(GameconnectCmd.MASTER_DATA_VERSION_CHECK_RESPONSE) self.result = 1 - self.update_flag = 0 - self.data_version = 100 + self.update_flag = 1 if game_ver != current_ver else 0 + self.data_version = current_ver def make(self) -> bytes: - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "update_flag" / Int8ul, # result is either 0 or 1 - "data_version" / Int32ub, - ) - - resp_data = resp_struct.build(dict( - result=self.result, - update_flag=self.update_flag, - data_version=self.data_version, - )) - - self.length = len(resp_data) + resp_data = encode_byte(self.result) + resp_data += encode_byte(self.update_flag) + resp_data += encode_int(self.data_version) return super().make() + resp_data -class SaoCommonGetAppVersionsRequest(SaoBaseRequest): +class SaoGetAppVersionsResponse(SaoBaseResponse): + def __init__(self) -> None: + super().__init__(GameconnectCmd.GET_APP_VERSIONS_RESPONSE) + self.result = 1 + self.data_list: List[AppVersionData] = [] + + def make(self) -> bytes: + ret = encode_byte(self.result) + ret += encode_arr_cls(self.data_list) + return super().make() + ret + +class SaoPayingPlayStartRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: super().__init__(header, data) + off = 0 + self.paying_user_id, new_off = decode_str(data, off) + off += new_off -class SaoCommonGetAppVersionsRequest(SaoBaseResponse): - def __init__(self, cmd) -> None: - super().__init__(cmd) + self.game_sub_id, new_off = decode_str(data, off) + off += new_off + + self.net_id, new_off = decode_str(data, off) + off += new_off + + self.place_id, new_off = decode_str(data, off) + off += new_off + + self.store_id, new_off = decode_str(data, off) + off += new_off + + self.store_name, new_off = decode_str(data, off) + off += new_off + + self.serial_no, new_off = decode_str(data, off) + off += new_off + +class SaoPayingPlayStartResponse(SaoBaseResponse): + def __init__(self, session_id: str = "1") -> None: + super().__init__(GameconnectCmd.PAYING_PLAY_START_RESPONSE) self.result = 1 - self.data_list_size = 1 # Number of arrays - - self.version_app_id = 1 - self.applying_start_date = "20230520193000" + self.paying_session_id = session_id def make(self) -> bytes: - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "data_list_size" / Int32ub, + return super().make() \ + + encode_byte(self.result) \ + + encode_str(self.paying_session_id) - "version_app_id" / Int32ub, - "applying_start_date_size" / Int32ub, # big endian - "applying_start_date" / Int16ul[len(self.applying_start_date)], - ) - - resp_data = resp_struct.build(dict( - result=self.result, - data_list_size=self.data_list_size, - - version_app_id=self.version_app_id, - applying_start_date_size=len(self.applying_start_date) * 2, - applying_start_date=[ord(x) for x in self.applying_start_date], - )) - - self.length = len(resp_data) - return super().make() + resp_data - -class SaoCommonPayingPlayStartRequest(SaoBaseRequest): +class SaoPayingPlayEndRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: super().__init__(header, data) + off = 0 + self.paying_session_id, new_off = decode_str(data, off) + off += new_off -class SaoCommonPayingPlayStartRequest(SaoBaseResponse): - def __init__(self, cmd) -> None: - super().__init__(cmd) - self.result = 1 - self.paying_session_id = "1" - - def make(self) -> bytes: - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "paying_session_id_size" / Int32ub, # big endian - "paying_session_id" / Int16ul[len(self.paying_session_id)], - ) + self.paying_user_id, new_off = decode_str(data, off) + off += new_off - resp_data = resp_struct.build(dict( - result=self.result, - paying_session_id_size=len(self.paying_session_id) * 2, - paying_session_id=[ord(x) for x in self.paying_session_id], - )) + self.net_id, new_off = decode_str(data, off) + off += new_off - self.length = len(resp_data) - return super().make() + resp_data + self.place_id, new_off = decode_str(data, off) + off += new_off + + self.played_date, new_off = decode_str(data, off) + off += new_off + + self.played_type = decode_short(data, off) + off += SHORT_OFF + + self.played_amount = decode_short(data, off) + off += SHORT_OFF + + self.store_id, new_off = decode_str(data, off) + off += new_off + + self.store_name, new_off = decode_str(data, off) + off += new_off + + self.serial_no, new_off = decode_str(data, off) + off += new_off class SaoGetAuthCardDataRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: @@ -207,7 +243,7 @@ class SaoGetAuthCardDataRequest(SaoBaseRequest): self.cabinet_type = decode_byte(data, off) off += BYTE_OFF - self.auth_type = decode_byte(data, off) + self.auth_type = AuthType(decode_byte(data, off)) # 0 is unknown, 1 is card (banapass, aime, AICC), 2 is moble off += BYTE_OFF store_id = decode_str(data, off) @@ -226,147 +262,90 @@ class SaoGetAuthCardDataRequest(SaoBaseRequest): self.chip_id = chip_id[0] off += chip_id[1] -class SaoGetAuthCardDataResponse(SaoBaseResponse): #GssSite.dll / GssSiteSystem / GameConnectProt / public class get_auth_card_data_R : GameConnect.GssProtocolBase - def __init__(self, cmd, profile_data) -> None: - super().__init__(cmd) - +class SaoGetAuthCardDataResponse(SaoBaseResponse): + def __init__(self, nicknname: str, user_id: int) -> None: + super().__init__(GameconnectCmd.GET_AUTH_CARD_DATA_RESPONSE) self.result = 1 self.unused_card_flag = "" self.first_play_flag = 0 self.tutorial_complete_flag = 1 - self.nick_name = profile_data['nick_name'] # nick_name field #4 - self.personal_id = str(profile_data['user']) + self.nick_name = nicknname + self.personal_id = str(user_id) def make(self) -> bytes: - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "unused_card_flag_size" / Int32ub, # big endian - "unused_card_flag" / Int16ul[len(self.unused_card_flag)], - "first_play_flag" / Int8ul, # result is either 0 or 1 - "tutorial_complete_flag" / Int8ul, # result is either 0 or 1 - "nick_name_size" / Int32ub, # big endian - "nick_name" / Int16ul[len(self.nick_name)], - "personal_id_size" / Int32ub, # big endian - "personal_id" / Int16ul[len(self.personal_id)] - ) + return super().make() \ + + encode_byte(self.result) \ + + encode_str(self.unused_card_flag) \ + + encode_byte(self.first_play_flag) \ + + encode_byte(self.tutorial_complete_flag) \ + + encode_str(self.nick_name) \ + + encode_str(self.personal_id) - resp_data = resp_struct.build(dict( - result=self.result, - unused_card_flag_size=len(self.unused_card_flag) * 2, - unused_card_flag=[ord(x) for x in self.unused_card_flag], - first_play_flag=self.first_play_flag, - tutorial_complete_flag=self.tutorial_complete_flag, - nick_name_size=len(self.nick_name) * 2, - nick_name=[ord(x) for x in self.nick_name], - personal_id_size=len(self.personal_id) * 2, - personal_id=[ord(x) for x in self.personal_id] - )) - - self.length = len(resp_data) - return super().make() + resp_data - -class SaoHomeCheckAcLoginBonusRequest(SaoBaseRequest): - def __init__(self, header: SaoRequestHeader, data: bytes) -> None: - super().__init__(header, data) - -class SaoHomeCheckAcLoginBonusResponse(SaoBaseResponse): - def __init__(self, cmd) -> None: - super().__init__(cmd) - self.result = 1 - self.reward_get_flag = 1 - self.get_ac_login_bonus_id_list_size = 2 # Array - - self.get_ac_login_bonus_id_1 = 1 # "2020年7月9日~(アニメ&リコリス記念)" - self.get_ac_login_bonus_id_2 = 2 # "2020年10月6日~(秋のデビュー&カムバックCP)" - - def make(self) -> bytes: - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "reward_get_flag" / Int8ul, # result is either 0 or 1 - "get_ac_login_bonus_id_list_size" / Int32ub, - - "get_ac_login_bonus_id_1" / Int32ub, - "get_ac_login_bonus_id_2" / Int32ub, - ) - - resp_data = resp_struct.build(dict( - result=self.result, - reward_get_flag=self.reward_get_flag, - get_ac_login_bonus_id_list_size=self.get_ac_login_bonus_id_list_size, - - get_ac_login_bonus_id_1=self.get_ac_login_bonus_id_1, - get_ac_login_bonus_id_2=self.get_ac_login_bonus_id_2, - )) - - self.length = len(resp_data) - return super().make() + resp_data - -class SaoGetQuestSceneMultiPlayPhotonServerRequest(SaoBaseRequest): - def __init__(self, header: SaoRequestHeader, data: bytes) -> None: - super().__init__(header, data) - -class SaoGetQuestSceneMultiPlayPhotonServerResponse(SaoBaseResponse): - def __init__(self, cmd) -> None: - super().__init__(cmd) - self.result = 1 - self.application_id = "7df3a2f6-d69d-4073-aafe-810ee61e1cea" - - def make(self) -> bytes: - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "application_id_size" / Int32ub, # big endian - "application_id" / Int16ul[len(self.application_id)], - ) - - resp_data = resp_struct.build(dict( - result=self.result, - application_id_size=len(self.application_id) * 2, - application_id=[ord(x) for x in self.application_id], - )) - - self.length = len(resp_data) - return super().make() + resp_data - -class SaoTicketRequest(SaoBaseRequest): - def __init__(self, header: SaoRequestHeader, data: bytes) -> None: - super().__init__(header, data) - -class SaoTicketResponse(SaoBaseResponse): - def __init__(self, cmd) -> None: - super().__init__(cmd) - self.result = "1" - self.ticket_id = "9" #up to 18 - - def make(self) -> bytes: - # create a resp struct - resp_struct = Struct( - "result_size" / Int32ub, # big endian - "result" / Int16ul[len(self.result)], - "ticket_id_size" / Int32ub, # big endian - "ticket_id" / Int16ul[len(self.result)], - ) - - resp_data = resp_struct.build(dict( - result_size=len(self.result) * 2, - result=[ord(x) for x in self.result], - ticket_id_size=len(self.ticket_id) * 2, - ticket_id=[ord(x) for x in self.ticket_id], - )) - - self.length = len(resp_data) - return super().make() + resp_data - -class SaoCommonLoginRequest(SaoBaseRequest): +class SaoGetAccessCodeByKeitaiRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: super().__init__(header, data) off = 0 self.cabinet_type = decode_byte(data, off) off += BYTE_OFF - self.auth_type = decode_byte(data, off) + self.auth_type = AuthType(decode_byte(data, off)) # 0 is unknown, 1 is card (banapass, aime, AICC), 2 is moble + off += BYTE_OFF + + self.store_id, new_off = decode_str(data, off) + off += new_off + + self.serial_no, new_off = decode_str(data, off) + off += new_off + + self.chip_id, new_off = decode_str(data, off) + off += new_off + +class SaoGetAccessCodeByKeitaiResponse(SaoBaseResponse): + def __init__(self, access_code: str) -> None: + super().__init__(GameconnectCmd.GET_ACCESS_CODE_BY_KEITAI_RESPONSE) + self.result = 1 + self.access_code = access_code + self.ba_id_flag = 1 + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_str(self.access_code) \ + + encode_byte(self.ba_id_flag) + +class SaoCheckAcLoginBonusResponse(SaoBaseResponse): + def __init__(self) -> None: + super().__init__(GameconnectCmd.CHECK_AC_LOGIN_BONUS_RESPONSE) + self.result = 1 + self.reward_get_flag = 1 + + self.get_ac_login_bonus_id_list: List[int] = [] # "2020年7月9日~(アニメ&リコリス記念)" + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_byte(self.reward_get_flag) \ + + encode_arr_num(self.get_ac_login_bonus_id_list, INT_OFF) \ + +class SaoGetQuestSceneMultiPlayPhotonServerResponse(SaoBaseResponse): + def __init__(self, app_id: str = "7df3a2f6-d69d-4073-aafe-810ee61e1cea") -> None: + super().__init__(GameconnectCmd.GET_QUEST_SCENE_MULTI_PLAY_PHOTON_SERVER_RESPONSE) + self.result = 1 + self.application_id = app_id + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_str(self.application_id) + +class SaoLoginRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.cabinet_type = decode_byte(data, off) + off += BYTE_OFF + + self.auth_type = AuthType(decode_byte(data, off)) # 0 is unknown, 1 is card (banapass, aime, AICC), 2 is moble off += BYTE_OFF store_id = decode_str(data, off) @@ -392,1107 +371,555 @@ class SaoCommonLoginRequest(SaoBaseRequest): self.free_ticket_distribution_target_flag = decode_byte(data, off) off += BYTE_OFF -class SaoCommonLoginResponse(SaoBaseResponse): - def __init__(self, cmd, profile_data) -> None: - super().__init__(cmd) +class SaoLoginResponse(SaoBaseResponse): + def __init__(self, user_id: int, first_play: bool = False, logged_in_today: bool = True) -> None: + super().__init__(GameconnectCmd.LOGIN_RESPONSE) self.result = 1 - self.user_id = str(profile_data['user']) - self.first_play_flag = 0 - self.grantable_free_ticket_flag = 1 - self.login_reward_vp = 99 - self.today_paying_flag = 1 + self.user_id = str(user_id) + self.first_play_flag = first_play + self.grantable_free_ticket_flag = not logged_in_today + self.login_reward_vp = 0 if logged_in_today else 100 + self.today_paying_flag = logged_in_today def make(self) -> bytes: - # create a resp struct - ''' - bool = Int8ul - short = Int16ub - int = Int32ub - ''' - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "user_id_size" / Int32ub, # big endian - "user_id" / Int16ul[len(self.user_id)], - "first_play_flag" / Int8ul, # result is either 0 or 1 - "grantable_free_ticket_flag" / Int8ul, # result is either 0 or 1 - "login_reward_vp" / Int16ub, - "today_paying_flag" / Int8ul, # result is either 0 or 1 - ) + return super().make() \ + + encode_byte(self.result) \ + + encode_str(self.user_id) \ + + encode_byte(self.first_play_flag) \ + + encode_byte(self.grantable_free_ticket_flag) \ + + encode_short(self.login_reward_vp) \ + + encode_byte(self.today_paying_flag) - resp_data = resp_struct.build(dict( - result=self.result, - user_id_size=len(self.user_id) * 2, - user_id=[ord(x) for x in self.user_id], - first_play_flag=self.first_play_flag, - grantable_free_ticket_flag=self.grantable_free_ticket_flag, - login_reward_vp=self.login_reward_vp, - today_paying_flag=self.today_paying_flag, - )) - - self.length = len(resp_data) - return super().make() + resp_data - -class SaoCheckComebackEventRequest(SaoBaseRequest): +class SaoLogoutRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: super().__init__(header, data) + off = 0 + self.ticket_id, new_off = decode_str(data, off) + off += new_off -class SaoCheckComebackEventRequest(SaoBaseResponse): - def __init__(self, cmd) -> None: - super().__init__(cmd) + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.cabinet_type = decode_byte(data, off) # 0 satalite, 1 terminal + off += BYTE_OFF + + self.remaining_ticket_num = decode_short(data, off) + off += SHORT_OFF + +class SaoPurchaseTicketRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.ticket_id, new_off = decode_str(data, off) + off += new_off + + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.discount_type = decode_byte(data, off) + off += BYTE_OFF + self.purchase_num = decode_byte(data, off) + off += BYTE_OFF + +class SaoConsumeTicketRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.ticket_id, new_off = decode_str(data, off) + off += new_off + + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.discount_type = decode_byte(data, off) + off += BYTE_OFF + self.act_type = ActTypeConsumeTicket(decode_byte(data, off)) + off += BYTE_OFF + self.consume_num = decode_byte(data, off) + off += BYTE_OFF + +class SaoAddCreditRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.ticket_id, new_off = decode_str(data, off) + off += new_off + + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.cabinet_type = decode_byte(data, off) + off += BYTE_OFF + self.add_num = decode_byte(data, off) + off += BYTE_OFF + +class SaoConsumeCreditRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.ticket_id, new_off = decode_str(data, off) + off += new_off + + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.store_id, new_off = decode_str(data, off) + off += new_off + + self.cabinet_type = decode_byte(data, off) + off += BYTE_OFF + self.act_type = ActTypeConsumeCredit(decode_byte(data, off)) + off += BYTE_OFF + self.consume_num = decode_byte(data, off) + off += BYTE_OFF + +class SaoPurchaseTicketGuestRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.store_id, new_off = decode_str(data, off) + off += new_off + + self.serial_no, new_off = decode_str(data, off) + off += new_off + + self.cabinet_type = decode_byte(data, off) + off += BYTE_OFF + self.discount_type = decode_byte(data, off) + off += BYTE_OFF + self.purchase_num = decode_byte(data, off) + off += BYTE_OFF + +class SaoConsumeTicketGuestRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.store_id, new_off = decode_str(data, off) + off += new_off + + self.serial_no, new_off = decode_str(data, off) + off += new_off + + self.cabinet_type = decode_byte(data, off) + off += BYTE_OFF + self.discount_type = decode_byte(data, off) + off += BYTE_OFF + self.act_type = ActTypeConsumeTicket(decode_byte(data, off)) + off += BYTE_OFF + self.consume_num = decode_byte(data, off) + off += BYTE_OFF + +class SaoAddCreditGuestRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.store_id, new_off = decode_str(data, off) + off += new_off + + self.serial_no, new_off = decode_str(data, off) + off += new_off + + self.cabinet_type = decode_byte(data, off) + off += BYTE_OFF + self.add_num = decode_byte(data, off) + off += BYTE_OFF + +class SaoConsumeCreditGuestRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.store_id, new_off = decode_str(data, off) + off += new_off + + self.serial_no, new_off = decode_str(data, off) + off += new_off + + self.cabinet_type = decode_byte(data, off) + off += BYTE_OFF + self.act_type = ActTypeConsumeCredit(decode_byte(data, off)) + off += BYTE_OFF + self.consume_num = decode_byte(data, off) + off += BYTE_OFF + +class SaoCheckComebackEventResponse(SaoBaseResponse): + def __init__(self) -> None: + super().__init__(GameconnectCmd.CHECK_COMEBACK_EVENT_RESPONSE) self.result = 1 - self.get_flag_ = 1 - self.get_comeback_event_id_list = "" # Array of events apparently + self.get_flag = 1 + self.get_comeback_event_id_list: List[int] = [] # Array of events apparently def make(self) -> bytes: - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "get_flag_" / Int8ul, # result is either 0 or 1 - "get_comeback_event_id_list_size" / Int32ub, # big endian - "get_comeback_event_id_list" / Int16ul[len(self.get_comeback_event_id_list)], - ) + return super().make() \ + + encode_byte(self.result) \ + + encode_byte(self.get_flag) \ + + encode_arr_num(self.get_comeback_event_id_list, INT_OFF) - resp_data = resp_struct.build(dict( - result=self.result, - get_flag_=self.get_flag_, - get_comeback_event_id_list_size=len(self.get_comeback_event_id_list) * 2, - get_comeback_event_id_list=[ord(x) for x in self.get_comeback_event_id_list], - )) +class SaoChangeMyStoreRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.ticket_id, new_off = decode_str(data, off) + off += new_off - self.length = len(resp_data) - return super().make() + resp_data + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.store_id, new_off = decode_str(data, off) + off += new_off + +class SaoCheckTitleGetDecisionRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.ticket_id, new_offset = decode_str(data, off) + off += new_offset + + self.user_id, new_offset = decode_str(data, off) + off += new_offset + +class SaoCheckTitleGetDecisionResponse(SaoBaseResponse): + def __init__(self) -> None: + super().__init__(GameconnectCmd.CHECK_TITLE_GET_DECISION_RESPONSE) + self.result = 1 + self.get_title_id_list: List[int] = [] + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_num(self.get_title_id_list, INT_OFF) class SaoGetUserBasicDataRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: super().__init__(header, data) - self.user_id = decode_str(data, 0)[0] + off = 0 + self.user_id, new_off = decode_str(data, off) + off += new_off class SaoGetUserBasicDataResponse(SaoBaseResponse): - def __init__(self, cmd, profile_data) -> None: - super().__init__(cmd) + def __init__(self, profile_data) -> None: + super().__init__(GameconnectCmd.GET_USER_BASIC_DATA_RESPONSE) self.result = 1 - self.user_basic_data_size = 1 # Number of arrays - self.user_type = profile_data['user_type'] - self.nick_name = profile_data['nick_name'] - self.rank_num = profile_data['rank_num'] - self.rank_exp = profile_data['rank_exp'] - self.own_col = profile_data['own_col'] - self.own_vp = profile_data['own_vp'] - self.own_yui_medal = profile_data['own_yui_medal'] - self.setting_title_id = profile_data['setting_title_id'] - self.favorite_user_hero_log_id = "" - self.favorite_user_support_log_id = "" - self.my_store_id = "1" - self.my_store_name = "ARTEMiS" - self.user_reg_date = "20230101120000" - + self.user_basic_data: List[UserBasicData] = [UserBasicData.from_args(profile_data)] + def make(self) -> bytes: - # create a resp struct - ''' - bool = Int8ul - short = Int16ub - int = Int32ub - ''' - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "user_basic_data_size" / Int32ub, + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.user_basic_data) - "user_type" / Int16ub, - "nick_name_size" / Int32ub, # big endian - "nick_name" / Int16ul[len(self.nick_name)], - "rank_num" / Int16ub, - "rank_exp" / Int32ub, - "own_col" / Int32ub, - "own_vp" / Int32ub, - "own_yui_medal" / Int32ub, - "setting_title_id" / Int32ub, - "favorite_user_hero_log_id_size" / Int32ub, # big endian - "favorite_user_hero_log_id" / Int16ul[len(str(self.favorite_user_hero_log_id))], - "favorite_user_support_log_id_size" / Int32ub, # big endian - "favorite_user_support_log_id" / Int16ul[len(str(self.favorite_user_support_log_id))], - "my_store_id_size" / Int32ub, # big endian - "my_store_id" / Int16ul[len(str(self.my_store_id))], - "my_store_name_size" / Int32ub, # big endian - "my_store_name" / Int16ul[len(str(self.my_store_name))], - "user_reg_date_size" / Int32ub, # big endian - "user_reg_date" / Int16ul[len(self.user_reg_date)] - - ) - - resp_data = resp_struct.build(dict( - result=self.result, - user_basic_data_size=self.user_basic_data_size, - - user_type=self.user_type, - nick_name_size=len(self.nick_name) * 2, - nick_name=[ord(x) for x in self.nick_name], - rank_num=self.rank_num, - rank_exp=self.rank_exp, - own_col=self.own_col, - own_vp=self.own_vp, - own_yui_medal=self.own_yui_medal, - setting_title_id=self.setting_title_id, - favorite_user_hero_log_id_size=len(self.favorite_user_hero_log_id) * 2, - favorite_user_hero_log_id=[ord(x) for x in str(self.favorite_user_hero_log_id)], - favorite_user_support_log_id_size=len(self.favorite_user_support_log_id) * 2, - favorite_user_support_log_id=[ord(x) for x in str(self.favorite_user_support_log_id)], - my_store_id_size=len(self.my_store_id) * 2, - my_store_id=[ord(x) for x in str(self.my_store_id)], - my_store_name_size=len(self.my_store_name) * 2, - my_store_name=[ord(x) for x in str(self.my_store_name)], - user_reg_date_size=len(self.user_reg_date) * 2, - user_reg_date=[ord(x) for x in self.user_reg_date], - )) - - self.length = len(resp_data) - return super().make() + resp_data - -class SaoGetHeroLogUserDataListRequest(SaoBaseRequest): +class SaoGetVpGashaTicketDataListRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: super().__init__(header, data) - self.user_id = decode_str(data, 0)[0] + off = 0 + self.user_id, new_off = decode_str(data, off) + off += new_off + +class SaoGetVpGashaTicketDataListResponse(SaoBaseResponse): + def __init__(self) -> None: + super().__init__(GameconnectCmd.GET_VP_GASHA_TICKET_DATA_LIST_RESPONSE) + self.result = 1 + self.vp_gasha_ticket_data_list: List[VpGashaTicketData] = [] + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.vp_gasha_ticket_data_list) + +class SaoChangeTitleRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.ticket_id, new_off = decode_str(data, off) + off += new_off + + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.user_title_id, new_off = decode_str(data, off) + off += new_off + +class SaoGetPresentBoxNumResponse(SaoBaseResponse): + def __init__(self, num_box: int = 0, max_num: int = 0) -> None: + super().__init__(GameconnectCmd.GET_PRESENT_BOX_NUM_RESPONSE) + self.result = 1 + self.num = num_box + self.max_num = max_num + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_short(self.num) \ + + encode_short(self.max_num) class SaoGetHeroLogUserDataListResponse(SaoBaseResponse): - def __init__(self, cmd, hero_data) -> None: - super().__init__(cmd) + def __init__(self) -> None: + super().__init__(GameconnectCmd.GET_HERO_LOG_USER_DATA_LIST_RESPONSE) self.result = 1 - self.user_hero_log_id = [] - self.log_level = [] - self.max_log_level_extended_num = [] - self.log_exp = [] - self.last_set_skill_slot1_skill_id = [] - self.last_set_skill_slot2_skill_id = [] - self.last_set_skill_slot3_skill_id = [] - self.last_set_skill_slot4_skill_id = [] - self.last_set_skill_slot5_skill_id = [] - - for i in range(len(hero_data)): - - # Calculate level based off experience and the CSV list - with open(r'titles/sao/data/HeroLogLevel.csv') as csv_file: - csv_reader = csv.reader(csv_file, delimiter=',') - line_count = 0 - data = [] - rowf = False - for row in csv_reader: - if rowf==False: - rowf=True - else: - data.append(row) - - exp = hero_data[i][4] - - for e in range(0,len(data)): - if exp>=int(data[e][1]) and exp bytes: - #new stuff - - hero_log_user_data_list_struct = Struct( - "user_hero_log_id_size" / Int32ub, # big endian - "user_hero_log_id" / Int16ul[9], #string - "hero_log_id" / Int32ub, #int - "log_level" / Int16ub, #short - "max_log_level_extended_num" / Int16ub, #short - "log_exp" / Int32ub, #int - "possible_awakening_flag" / Int8ul, # result is either 0 or 1 - "awakening_stage" / Int16ub, #short - "awakening_exp" / Int32ub, #int - "skill_slot_correction_value" / Int8ul, # result is either 0 or 1 - "last_set_skill_slot1_skill_id" / Int16ub, #short - "last_set_skill_slot2_skill_id" / Int16ub, #short - "last_set_skill_slot3_skill_id" / Int16ub, #short - "last_set_skill_slot4_skill_id" / Int16ub, #short - "last_set_skill_slot5_skill_id" / Int16ub, #short - "property1_property_id" / Int32ub, - "property1_value1" / Int32ub, - "property1_value2" / Int32ub, - "property2_property_id" / Int32ub, - "property2_value1" / Int32ub, - "property2_value2" / Int32ub, - "property3_property_id" / Int32ub, - "property3_value1" / Int32ub, - "property3_value2" / Int32ub, - "property4_property_id" / Int32ub, - "property4_value1" / Int32ub, - "property4_value2" / Int32ub, - "converted_card_num" / Int16ub, - "shop_purchase_flag" / Int8ul, # result is either 0 or 1 - "protect_flag" / Int8ul, # result is either 0 or 1 - "get_date_size" / Int32ub, # big endian - "get_date" / Int16ul[len(self.get_date)], - ) - - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "hero_log_user_data_list_size" / Rebuild(Int32ub, len_(this.hero_log_user_data_list)), # big endian - "hero_log_user_data_list" / Array(this.hero_log_user_data_list_size, hero_log_user_data_list_struct), - ) - - resp_data = resp_struct.parse(resp_struct.build(dict( - result=self.result, - hero_log_user_data_list_size=0, - hero_log_user_data_list=[], - ))) - - for i in range(len(self.hero_log_id)): - hero_data = dict( - user_hero_log_id_size=len(self.user_hero_log_id[i]) * 2, - user_hero_log_id=[ord(x) for x in self.user_hero_log_id[i]], - hero_log_id=self.hero_log_id[i], - log_level=self.log_level[i], - max_log_level_extended_num=self.max_log_level_extended_num[i], - log_exp=self.log_exp[i], - possible_awakening_flag=self.possible_awakening_flag, - awakening_stage=self.awakening_stage, - awakening_exp=self.awakening_exp, - skill_slot_correction_value=self.skill_slot_correction_value, - last_set_skill_slot1_skill_id=self.last_set_skill_slot1_skill_id[i], - last_set_skill_slot2_skill_id=self.last_set_skill_slot2_skill_id[i], - last_set_skill_slot3_skill_id=self.last_set_skill_slot3_skill_id[i], - last_set_skill_slot4_skill_id=self.last_set_skill_slot4_skill_id[i], - last_set_skill_slot5_skill_id=self.last_set_skill_slot5_skill_id[i], - property1_property_id=self.property1_property_id, - property1_value1=self.property1_value1, - property1_value2=self.property1_value2, - property2_property_id=self.property2_property_id, - property2_value1=self.property2_value1, - property2_value2=self.property2_value2, - property3_property_id=self.property3_property_id, - property3_value1=self.property3_value1, - property3_value2=self.property3_value2, - property4_property_id=self.property4_property_id, - property4_value1=self.property4_value1, - property4_value2=self.property4_value2, - converted_card_num=self.converted_card_num, - shop_purchase_flag=self.shop_purchase_flag, - protect_flag=self.protect_flag, - get_date_size=len(self.get_date) * 2, - get_date=[ord(x) for x in self.get_date], - - ) - - resp_data.hero_log_user_data_list.append(hero_data) - - resp_data["hero_log_user_data_list_size"] = len(resp_data.hero_log_user_data_list) - - # finally, rebuild the resp_data - resp_data = resp_struct.build(resp_data) - - self.length = len(resp_data) - return super().make() + resp_data - -class SaoGetEquipmentUserDataListRequest(SaoBaseRequest): - def __init__(self, header: SaoRequestHeader, data: bytes) -> None: - super().__init__(header, data) - self.user_id = decode_str(data, 0)[0] + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.hero_log_user_data_list) class SaoGetEquipmentUserDataListResponse(SaoBaseResponse): - def __init__(self, cmd, equipment_data) -> None: - super().__init__(cmd) + def __init__(self) -> None: + super().__init__(GameconnectCmd.GET_EQUIPMENT_USER_DATA_LIST_RESPONSE) self.result = 1 - - self.user_equipment_id = [] - self.enhancement_value = [] - self.max_enhancement_value_extended_num = [] - self.enhancement_exp = [] - self.awakening_stage = [] - self.awakening_exp = [] - self.possible_awakening_flag = [] - equipment_level = 0 - - for i in range(len(equipment_data)): - - # Calculate level based off experience and the CSV list - with open(r'titles/sao/data/EquipmentLevel.csv') as csv_file: - csv_reader = csv.reader(csv_file, delimiter=',') - line_count = 0 - data = [] - rowf = False - for row in csv_reader: - if rowf==False: - rowf=True - else: - data.append(row) - - exp = equipment_data[i][4] - - for e in range(0,len(data)): - if exp>=int(data[e][1]) and exp bytes: - - equipment_user_data_list_struct = Struct( - "user_equipment_id_size" / Int32ub, # big endian - "user_equipment_id" / Int16ul[9], #string - "equipment_id" / Int32ub, #int - "enhancement_value" / Int16ub, #short - "max_enhancement_value_extended_num" / Int16ub, #short - "enhancement_exp" / Int32ub, #int - "possible_awakening_flag" / Int8ul, # result is either 0 or 1 - "awakening_stage" / Int16ub, #short - "awakening_exp" / Int32ub, #int - "property1_property_id" / Int32ub, - "property1_value1" / Int32ub, - "property1_value2" / Int32ub, - "property2_property_id" / Int32ub, - "property2_value1" / Int32ub, - "property2_value2" / Int32ub, - "property3_property_id" / Int32ub, - "property3_value1" / Int32ub, - "property3_value2" / Int32ub, - "property4_property_id" / Int32ub, - "property4_value1" / Int32ub, - "property4_value2" / Int32ub, - "converted_card_num" / Int16ub, - "shop_purchase_flag" / Int8ul, # result is either 0 or 1 - "protect_flag" / Int8ul, # result is either 0 or 1 - "get_date_size" / Int32ub, # big endian - "get_date" / Int16ul[len(self.get_date)], - ) - - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "equipment_user_data_list_size" / Rebuild(Int32ub, len_(this.equipment_user_data_list)), # big endian - "equipment_user_data_list" / Array(this.equipment_user_data_list_size, equipment_user_data_list_struct), - ) - - resp_data = resp_struct.parse(resp_struct.build(dict( - result=self.result, - equipment_user_data_list_size=0, - equipment_user_data_list=[], - ))) - - for i in range(len(self.equipment_id)): - equipment_data = dict( - user_equipment_id_size=len(self.user_equipment_id[i]) * 2, - user_equipment_id=[ord(x) for x in self.user_equipment_id[i]], - equipment_id=self.equipment_id[i], - enhancement_value=self.enhancement_value[i], - max_enhancement_value_extended_num=self.max_enhancement_value_extended_num[i], - enhancement_exp=self.enhancement_exp[i], - possible_awakening_flag=self.possible_awakening_flag[i], - awakening_stage=self.awakening_stage[i], - awakening_exp=self.awakening_exp[i], - property1_property_id=self.property1_property_id, - property1_value1=self.property1_value1, - property1_value2=self.property1_value2, - property2_property_id=self.property2_property_id, - property2_value1=self.property2_value1, - property2_value2=self.property2_value2, - property3_property_id=self.property3_property_id, - property3_value1=self.property3_value1, - property3_value2=self.property3_value2, - property4_property_id=self.property4_property_id, - property4_value1=self.property4_value1, - property4_value2=self.property4_value2, - converted_card_num=self.converted_card_num, - shop_purchase_flag=self.shop_purchase_flag, - protect_flag=self.protect_flag, - get_date_size=len(self.get_date) * 2, - get_date=[ord(x) for x in self.get_date], - - ) - - resp_data.equipment_user_data_list.append(equipment_data) - - resp_data["equipment_user_data_list_size"] = len(resp_data.equipment_user_data_list) - - # finally, rebuild the resp_data - resp_data = resp_struct.build(resp_data) - - self.length = len(resp_data) - return super().make() + resp_data - -class SaoGetItemUserDataListRequest(SaoBaseRequest): - def __init__(self, header: SaoRequestHeader, data: bytes) -> None: - super().__init__(header, data) - self.user_id = decode_str(data, 0)[0] + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.equipment_user_data_list) class SaoGetItemUserDataListResponse(SaoBaseResponse): - def __init__(self, cmd, item_data) -> None: - super().__init__(cmd) + def __init__(self, item_data: List[Dict] = []) -> None: + super().__init__(GameconnectCmd.GET_ITEM_USER_DATA_LIST_RESPONSE) self.result = 1 + self.item_user_data_list: List[ItemUserData] = [] - self.user_item_id = [] - - for i in range(len(item_data)): - self.user_item_id.append(item_data[i][2]) - - # item_user_data_list - self.user_item_id = list(map(str,self.user_item_id)) #str - self.item_id = list(map(int,self.user_item_id)) #int - self.protect_flag = 0 #byte - self.get_date = "20230101120000" #str + if item_data: + for item in item_data: + self.item_user_data_list.append(ItemUserData.from_args(item)) def make(self) -> bytes: - #new stuff - - item_user_data_list_struct = Struct( - "user_item_id_size" / Int32ub, # big endian - "user_item_id" / Int16ul[6], #string but this will not work with 10000 IDs... only with 6 digits - "item_id" / Int32ub, #int - "protect_flag" / Int8ul, # result is either 0 or 1 - "get_date_size" / Int32ub, # big endian - "get_date" / Int16ul[len(self.get_date)], - ) - - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "item_user_data_list_size" / Rebuild(Int32ub, len_(this.item_user_data_list)), # big endian - "item_user_data_list" / Array(this.item_user_data_list_size, item_user_data_list_struct), - ) - - resp_data = resp_struct.parse(resp_struct.build(dict( - result=self.result, - item_user_data_list_size=0, - item_user_data_list=[], - ))) - - for i in range(len(self.item_id)): - item_data = dict( - user_item_id_size=len(self.user_item_id[i]) * 2, - user_item_id=[ord(x) for x in self.user_item_id[i]], - item_id=self.item_id[i], - protect_flag=self.protect_flag, - get_date_size=len(self.get_date) * 2, - get_date=[ord(x) for x in self.get_date], - - ) - - resp_data.item_user_data_list.append(item_data) - - resp_data["item_user_data_list_size"] = len(resp_data.item_user_data_list) - - # finally, rebuild the resp_data - resp_data = resp_struct.build(resp_data) - - self.length = len(resp_data) - return super().make() + resp_data - -class SaoGetSupportLogUserDataListRequest(SaoBaseRequest): - def __init__(self, header: SaoRequestHeader, data: bytes) -> None: - super().__init__(header, data) + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.item_user_data_list) class SaoGetSupportLogUserDataListResponse(SaoBaseResponse): - def __init__(self, cmd, supportIdsData) -> None: - super().__init__(cmd) + def __init__(self) -> None: + super().__init__(GameconnectCmd.GET_SUPPORT_LOG_USER_DATA_LIST_RESPONSE) self.result = 1 + self.support_log_user_data_list: List[SupportLogUserData] = [] - # support_log_user_data_list - self.user_support_log_id = list(map(str,supportIdsData)) #str - self.support_log_id = supportIdsData #int - self.possible_awakening_flag = 0 - self.awakening_stage = 0 - self.awakening_exp = 0 - self.converted_card_num = 0 - self.shop_purchase_flag = 0 - self.protect_flag = 0 #byte - self.get_date = "20230101120000" #str - def make(self) -> bytes: - support_log_user_data_list_struct = Struct( - "user_support_log_id_size" / Int32ub, # big endian - "user_support_log_id" / Int16ul[9], - "support_log_id" / Int32ub, #int - "possible_awakening_flag" / Int8ul, # result is either 0 or 1 - "awakening_stage" / Int16ub, #short - "awakening_exp" / Int32ub, # int - "converted_card_num" / Int16ub, #short - "shop_purchase_flag" / Int8ul, # result is either 0 or 1 - "protect_flag" / Int8ul, # result is either 0 or 1 - "get_date_size" / Int32ub, # big endian - "get_date" / Int16ul[len(self.get_date)], - ) - - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "support_log_user_data_list_size" / Rebuild(Int32ub, len_(this.support_log_user_data_list)), # big endian - "support_log_user_data_list" / Array(this.support_log_user_data_list_size, support_log_user_data_list_struct), - ) - - resp_data = resp_struct.parse(resp_struct.build(dict( - result=self.result, - support_log_user_data_list_size=0, - support_log_user_data_list=[], - ))) - - for i in range(len(self.support_log_id)): - support_data = dict( - user_support_log_id_size=len(self.user_support_log_id[i]) * 2, - user_support_log_id=[ord(x) for x in self.user_support_log_id[i]], - support_log_id=self.support_log_id[i], - possible_awakening_flag=self.possible_awakening_flag, - awakening_stage=self.awakening_stage, - awakening_exp=self.awakening_exp, - converted_card_num=self.converted_card_num, - shop_purchase_flag=self.shop_purchase_flag, - protect_flag=self.protect_flag, - get_date_size=len(self.get_date) * 2, - get_date=[ord(x) for x in self.get_date], - - ) - - resp_data.support_log_user_data_list.append(support_data) - - resp_data["support_log_user_data_list_size"] = len(resp_data.support_log_user_data_list) - - resp_data = resp_struct.build(resp_data) - - self.length = len(resp_data) - return super().make() + resp_data - -class SaoGetTitleUserDataListRequest(SaoBaseRequest): - def __init__(self, header: SaoRequestHeader, data: bytes) -> None: - super().__init__(header, data) + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.support_log_user_data_list) class SaoGetTitleUserDataListResponse(SaoBaseResponse): - def __init__(self, cmd, titleIdsData) -> None: - super().__init__(cmd) + def __init__(self, user_id: str, titles: List[int]) -> None: + super().__init__(GameconnectCmd.GET_TITLE_USER_DATA_LIST_RESPONSE) self.result = 1 + self.title_user_data_list: List[TitleUserData] = [] + + if titles: + for x in titles: + self.title_user_data_list.append(TitleUserData.from_args(user_id + str(x), x)) - # title_user_data_list - self.user_title_id = list(map(str,titleIdsData)) #str - self.title_id = titleIdsData #int - def make(self) -> bytes: - title_user_data_list_struct = Struct( - "user_title_id_size" / Int32ub, # big endian - "user_title_id" / Int16ul[6], #string - "title_id" / Int32ub, #int - ) - - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "title_user_data_list_size" / Rebuild(Int32ub, len_(this.title_user_data_list)), # big endian - "title_user_data_list" / Array(this.title_user_data_list_size, title_user_data_list_struct), - ) - - resp_data = resp_struct.parse(resp_struct.build(dict( - result=self.result, - title_user_data_list_size=0, - title_user_data_list=[], - ))) - - for i in range(len(self.title_id)): - title_data = dict( - user_title_id_size=len(self.user_title_id[i]) * 2, - user_title_id=[ord(x) for x in self.user_title_id[i]], - title_id=self.title_id[i], - ) - - resp_data.title_user_data_list.append(title_data) - - resp_data["title_user_data_list_size"] = len(resp_data.title_user_data_list) - - # finally, rebuild the resp_data - resp_data = resp_struct.build(resp_data) - - self.length = len(resp_data) - return super().make() + resp_data - -class SaoGetEpisodeAppendDataListRequest(SaoBaseRequest): - def __init__(self, header: SaoRequestHeader, data: bytes) -> None: - super().__init__(header, data) - self.user_id = decode_str(data, 0)[0] + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.title_user_data_list) class SaoGetEpisodeAppendDataListResponse(SaoBaseResponse): - def __init__(self, cmd, profile_data) -> None: - super().__init__(cmd) - self.length = None + def __init__(self) -> None: + super().__init__(GameconnectCmd.GET_EPISODE_APPEND_DATA_LIST_RESPONSE) self.result = 1 - - self.user_episode_append_id_list = ["10001", "10002", "10003", "10004", "10005"] - self.user_id_list = [str(profile_data["user"]), str(profile_data["user"]), str(profile_data["user"]), str(profile_data["user"]), str(profile_data["user"])] - self.episode_append_id_list = [10001, 10002, 10003, 10004, 10005] - self.own_num_list = [3, 3, 3, 3 ,3] + self.episode_append_data_list: List[EpisodeAppendUserData] = [] def make(self) -> bytes: - episode_data_struct = Struct( - "user_episode_append_id_size" / Rebuild(Int32ub, len_(this.user_episode_append_id) * 2), # calculates the length of the user_episode_append_id - "user_episode_append_id" / PaddedString(this.user_episode_append_id_size, "utf_16_le"), # user_episode_append_id is a (zero) padded string - "user_id_size" / Rebuild(Int32ub, len_(this.user_id) * 2), # calculates the length of the user_id - "user_id" / PaddedString(this.user_id_size, "utf_16_le"), # user_id is a (zero) padded string - "episode_append_id" / Int32ub, - "own_num" / Int32ub, - ) + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.episode_append_data_list) - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "episode_append_data_list_size" / Rebuild(Int32ub, len_(this.episode_append_data_list)), # big endian - "episode_append_data_list" / Array(this.episode_append_data_list_size, episode_data_struct), - ) - - # really dump to parse the build resp, but that creates a new object - # and is nicer to twork with - resp_data = resp_struct.parse(resp_struct.build(dict( - result=self.result, - episode_append_data_list_size=0, - episode_append_data_list=[], - ))) - - if len(self.user_episode_append_id_list) != len(self.user_id_list) != len(self.episode_append_id_list) != len(self.own_num_list): - raise ValueError("all lists must be of the same length") - - for i in range(len(self.user_id_list)): - # add the episode_data_struct to the resp_struct.episode_append_data_list - resp_data.episode_append_data_list.append(dict( - user_episode_append_id=self.user_episode_append_id_list[i], - user_id=self.user_id_list[i], - episode_append_id=self.episode_append_id_list[i], - own_num=self.own_num_list[i], - )) - - resp_data["episode_append_data_list_size"] = len(resp_data.episode_append_data_list) - - # finally, rebuild the resp_data - resp_data = resp_struct.build(resp_data) - - self.length = len(resp_data) - return super().make() + resp_data - -class SaoGetPartyDataListRequest(SaoBaseRequest): - def __init__(self, header: SaoRequestHeader, data: bytes) -> None: - super().__init__(header, data) - self.user_id = decode_str(data, 0)[0] - -class SaoGetPartyDataListResponse(SaoBaseResponse): # Default party - def __init__(self, cmd, hero1_data, hero2_data, hero3_data) -> None: - super().__init__(cmd) - +class SaoGetEventItemDataListResponse(SaoBaseResponse): + def __init__(self) -> None: + super().__init__(GameconnectCmd.GET_EVENT_ITEM_DATA_LIST_RESPONSE) self.result = 1 - self.party_data_list_size = 1 # Number of arrays - - self.user_party_id = "0" - self.team_no = 0 - self.party_team_data_list_size = 3 # Number of arrays - - self.user_party_team_id_1 = "0" - self.arrangement_num_1 = 0 - self.user_hero_log_id_1 = str(hero1_data[2]) - self.main_weapon_user_equipment_id_1 = str(hero1_data[5]) - self.sub_equipment_user_equipment_id_1 = str(hero1_data[6]) - self.skill_slot1_skill_id_1 = hero1_data[7] - self.skill_slot2_skill_id_1 = hero1_data[8] - self.skill_slot3_skill_id_1 = hero1_data[9] - self.skill_slot4_skill_id_1 = hero1_data[10] - self.skill_slot5_skill_id_1 = hero1_data[11] - - self.user_party_team_id_2 = "0" - self.arrangement_num_2 = 0 - self.user_hero_log_id_2 = str(hero2_data[2]) - self.main_weapon_user_equipment_id_2 = str(hero2_data[5]) - self.sub_equipment_user_equipment_id_2 = str(hero2_data[6]) - self.skill_slot1_skill_id_2 = hero2_data[7] - self.skill_slot2_skill_id_2 = hero2_data[8] - self.skill_slot3_skill_id_2 = hero2_data[9] - self.skill_slot4_skill_id_2 = hero2_data[10] - self.skill_slot5_skill_id_2 = hero2_data[11] - - self.user_party_team_id_3 = "0" - self.arrangement_num_3 = 0 - self.user_hero_log_id_3 = str(hero3_data[2]) - self.main_weapon_user_equipment_id_3 = str(hero3_data[5]) - self.sub_equipment_user_equipment_id_3 = str(hero3_data[6]) - self.skill_slot1_skill_id_3 = hero3_data[7] - self.skill_slot2_skill_id_3 = hero3_data[8] - self.skill_slot3_skill_id_3 = hero3_data[9] - self.skill_slot4_skill_id_3 = hero3_data[10] - self.skill_slot5_skill_id_3 = hero3_data[11] + self.event_item_data_list: List[EventItemUserData] = [] def make(self) -> bytes: - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "party_data_list_size" / Int32ub, # big endian + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.event_item_data_list) - "user_party_id_size" / Int32ub, # big endian - "user_party_id" / Int16ul[len(self.user_party_id)], - "team_no" / Int8ul, # result is either 0 or 1 - "party_team_data_list_size" / Int32ub, # big endian - - "user_party_team_id_1_size" / Int32ub, # big endian - "user_party_team_id_1" / Int16ul[len(self.user_party_team_id_1)], - "arrangement_num_1" / Int8ul, # big endian - "user_hero_log_id_1_size" / Int32ub, # big endian - "user_hero_log_id_1" / Int16ul[len(self.user_hero_log_id_1)], - "main_weapon_user_equipment_id_1_size" / Int32ub, # big endian - "main_weapon_user_equipment_id_1" / Int16ul[len(self.main_weapon_user_equipment_id_1)], - "sub_equipment_user_equipment_id_1_size" / Int32ub, # big endian - "sub_equipment_user_equipment_id_1" / Int16ul[len(self.sub_equipment_user_equipment_id_1)], - "skill_slot1_skill_id_1" / Int32ub, - "skill_slot2_skill_id_1" / Int32ub, - "skill_slot3_skill_id_1" / Int32ub, - "skill_slot4_skill_id_1" / Int32ub, - "skill_slot5_skill_id_1" / Int32ub, - - "user_party_team_id_2_size" / Int32ub, # big endian - "user_party_team_id_2" / Int16ul[len(self.user_party_team_id_2)], - "arrangement_num_2" / Int8ul, # result is either 0 or 1 - "user_hero_log_id_2_size" / Int32ub, # big endian - "user_hero_log_id_2" / Int16ul[len(self.user_hero_log_id_2)], - "main_weapon_user_equipment_id_2_size" / Int32ub, # big endian - "main_weapon_user_equipment_id_2" / Int16ul[len(self.main_weapon_user_equipment_id_2)], - "sub_equipment_user_equipment_id_2_size" / Int32ub, # big endian - "sub_equipment_user_equipment_id_2" / Int16ul[len(self.sub_equipment_user_equipment_id_2)], - "skill_slot1_skill_id_2" / Int32ub, - "skill_slot2_skill_id_2" / Int32ub, - "skill_slot3_skill_id_2" / Int32ub, - "skill_slot4_skill_id_2" / Int32ub, - "skill_slot5_skill_id_2" / Int32ub, - - "user_party_team_id_3_size" / Int32ub, # big endian - "user_party_team_id_3" / Int16ul[len(self.user_party_team_id_3)], - "arrangement_num_3" / Int8ul, # result is either 0 or 1 - "user_hero_log_id_3_size" / Int32ub, # big endian - "user_hero_log_id_3" / Int16ul[len(self.user_hero_log_id_3)], - "main_weapon_user_equipment_id_3_size" / Int32ub, # big endian - "main_weapon_user_equipment_id_3" / Int16ul[len(self.main_weapon_user_equipment_id_3)], - "sub_equipment_user_equipment_id_3_size" / Int32ub, # big endian - "sub_equipment_user_equipment_id_3" / Int16ul[len(self.sub_equipment_user_equipment_id_3)], - "skill_slot1_skill_id_3" / Int32ub, - "skill_slot2_skill_id_3" / Int32ub, - "skill_slot3_skill_id_3" / Int32ub, - "skill_slot4_skill_id_3" / Int32ub, - "skill_slot5_skill_id_3" / Int32ub, - - ) - - resp_data = resp_struct.build(dict( - result=self.result, - party_data_list_size=self.party_data_list_size, - - user_party_id_size=len(self.user_party_id) * 2, - user_party_id=[ord(x) for x in self.user_party_id], - team_no=self.team_no, - party_team_data_list_size=self.party_team_data_list_size, - - user_party_team_id_1_size=len(self.user_party_team_id_1) * 2, - user_party_team_id_1=[ord(x) for x in self.user_party_team_id_1], - arrangement_num_1=self.arrangement_num_1, - user_hero_log_id_1_size=len(self.user_hero_log_id_1) * 2, - user_hero_log_id_1=[ord(x) for x in self.user_hero_log_id_1], - main_weapon_user_equipment_id_1_size=len(self.main_weapon_user_equipment_id_1) * 2, - main_weapon_user_equipment_id_1=[ord(x) for x in self.main_weapon_user_equipment_id_1], - sub_equipment_user_equipment_id_1_size=len(self.sub_equipment_user_equipment_id_1) * 2, - sub_equipment_user_equipment_id_1=[ord(x) for x in self.sub_equipment_user_equipment_id_1], - skill_slot1_skill_id_1=self.skill_slot1_skill_id_1, - skill_slot2_skill_id_1=self.skill_slot2_skill_id_1, - skill_slot3_skill_id_1=self.skill_slot3_skill_id_1, - skill_slot4_skill_id_1=self.skill_slot4_skill_id_1, - skill_slot5_skill_id_1=self.skill_slot5_skill_id_1, - - user_party_team_id_2_size=len(self.user_party_team_id_2) * 2, - user_party_team_id_2=[ord(x) for x in self.user_party_team_id_2], - arrangement_num_2=self.arrangement_num_2, - user_hero_log_id_2_size=len(self.user_hero_log_id_2) * 2, - user_hero_log_id_2=[ord(x) for x in self.user_hero_log_id_2], - main_weapon_user_equipment_id_2_size=len(self.main_weapon_user_equipment_id_2) * 2, - main_weapon_user_equipment_id_2=[ord(x) for x in self.main_weapon_user_equipment_id_2], - sub_equipment_user_equipment_id_2_size=len(self.sub_equipment_user_equipment_id_2) * 2, - sub_equipment_user_equipment_id_2=[ord(x) for x in self.sub_equipment_user_equipment_id_2], - skill_slot1_skill_id_2=self.skill_slot1_skill_id_2, - skill_slot2_skill_id_2=self.skill_slot2_skill_id_2, - skill_slot3_skill_id_2=self.skill_slot3_skill_id_2, - skill_slot4_skill_id_2=self.skill_slot4_skill_id_2, - skill_slot5_skill_id_2=self.skill_slot5_skill_id_2, - - user_party_team_id_3_size=len(self.user_party_team_id_3) * 2, - user_party_team_id_3=[ord(x) for x in self.user_party_team_id_3], - arrangement_num_3=self.arrangement_num_3, - user_hero_log_id_3_size=len(self.user_hero_log_id_3) * 2, - user_hero_log_id_3=[ord(x) for x in self.user_hero_log_id_3], - main_weapon_user_equipment_id_3_size=len(self.main_weapon_user_equipment_id_3) * 2, - main_weapon_user_equipment_id_3=[ord(x) for x in self.main_weapon_user_equipment_id_3], - sub_equipment_user_equipment_id_3_size=len(self.sub_equipment_user_equipment_id_3) * 2, - sub_equipment_user_equipment_id_3=[ord(x) for x in self.sub_equipment_user_equipment_id_3], - skill_slot1_skill_id_3=self.skill_slot1_skill_id_3, - skill_slot2_skill_id_3=self.skill_slot2_skill_id_3, - skill_slot3_skill_id_3=self.skill_slot3_skill_id_3, - skill_slot4_skill_id_3=self.skill_slot4_skill_id_3, - skill_slot5_skill_id_3=self.skill_slot5_skill_id_3, - )) - - self.length = len(resp_data) - return super().make() + resp_data +class SaoGetGashaMedalUserDataListResponse(SaoBaseResponse): + def __init__(self) -> None: + super().__init__(GameconnectCmd.GET_GASHA_MEDAL_USER_DATA_LIST_RESPONSE) + self.result = 1 + self.data_list: List[GashaMedalUserData] = [] -class SaoGetQuestScenePrevScanProfileCardRequest(SaoBaseRequest): - def __init__(self, header: SaoRequestHeader, data: bytes) -> None: - super().__init__(header, data) + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetPartyDataListResponse(SaoBaseResponse): + def __init__(self) -> None: + super().__init__(GameconnectCmd.GET_PARTY_DATA_LIST_RESPONSE) + self.result = 1 + self.party_data_list: List[PartyData] = [] + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.party_data_list) + +class SaoGetSupportLogPartyDataListResponse(SaoBaseResponse): + def __init__(self) -> None: + super().__init__(GameconnectCmd.GET_SUPPORT_LOG_PARTY_DATA_LIST_RESPONSE) + self.result = 1 + self.support_log_party_data_list: List[SupportLogPartyData] = [] + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.support_log_party_data_list) class SaoGetQuestScenePrevScanProfileCardResponse(SaoBaseResponse): - def __init__(self, cmd) -> None: - super().__init__(cmd) + def __init__(self) -> None: + super().__init__(GameconnectCmd.GET_QUEST_SCENE_PREV_SCAN_PROFILE_CARD_RESPONSE) self.result = 1 - self.profile_card_data = 1 # number of arrays - - self.profile_card_code = "" - self.nick_name = "" - + self.profile_card_data: List[ReadProfileCardData] = [] + def make(self) -> bytes: - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "profile_card_data" / Int32ub, # big endian - - "profile_card_code_size" / Int32ub, # big endian - "profile_card_code" / Int16ul[len(self.profile_card_code)], - "nick_name_size" / Int32ub, # big endian - "nick_name" / Int16ul[len(self.nick_name)], - "rank_num" / Int16ub, #short - "setting_title_id" / Int32ub, # int - "skill_id" / Int16ub, # short - "hero_log_hero_log_id" / Int32ub, # int - "hero_log_log_level" / Int16ub, # short - "hero_log_awakening_stage" / Int16ub, # short - "hero_log_property1_property_id" / Int32ub, # int - "hero_log_property1_value1" / Int32ub, # int - "hero_log_property1_value2" / Int32ub, # int - "hero_log_property2_property_id" / Int32ub, # int - "hero_log_property2_value1" / Int32ub, # int - "hero_log_property2_value2" / Int32ub, # int - "hero_log_property3_property_id" / Int32ub, # int - "hero_log_property3_value1" / Int32ub, # int - "hero_log_property3_value2" / Int32ub, # int - "hero_log_property4_property_id" / Int32ub, # int - "hero_log_property4_value1" / Int32ub, # int - "hero_log_property4_value2" / Int32ub, # int - "main_weapon_equipment_id" / Int32ub, # int - "main_weapon_enhancement_value" / Int16ub, # short - "main_weapon_awakening_stage" / Int16ub, # short - "main_weapon_property1_property_id" / Int32ub, # int - "main_weapon_property1_value1" / Int32ub, # int - "main_weapon_property1_value2" / Int32ub, # int - "main_weapon_property2_property_id" / Int32ub, # int - "main_weapon_property2_value1" / Int32ub, # int - "main_weapon_property2_value2" / Int32ub, # int - "main_weapon_property3_property_id" / Int32ub, # int - "main_weapon_property3_value1" / Int32ub, # int - "main_weapon_property3_value2" / Int32ub, # int - "main_weapon_property4_property_id" / Int32ub, # int - "main_weapon_property4_value1" / Int32ub, # int - "main_weapon_property4_value2" / Int32ub, # int - "sub_equipment_equipment_id" / Int32ub, # int - "sub_equipment_enhancement_value" / Int16ub, # short - "sub_equipment_awakening_stage" / Int16ub, # short - "sub_equipment_property1_property_id" / Int32ub, # int - "sub_equipment_property1_value1" / Int32ub, # int - "sub_equipment_property1_value2" / Int32ub, # int - "sub_equipment_property2_property_id" / Int32ub, # int - "sub_equipment_property2_value1" / Int32ub, # int - "sub_equipment_property2_value2" / Int32ub, # int - "sub_equipment_property3_property_id" / Int32ub, # int - "sub_equipment_property3_value1" / Int32ub, # int - "sub_equipment_property3_value2" / Int32ub, # int - "sub_equipment_property4_property_id" / Int32ub, # int - "sub_equipment_property4_value1" / Int32ub, # int - "sub_equipment_property4_value2" / Int32ub, # int - "holographic_flag" / Int8ul, # result is either 0 or 1 - ) - - resp_data = resp_struct.build(dict( - result=self.result, - profile_card_data=self.profile_card_data, - - profile_card_code_size=len(self.profile_card_code) * 2, - profile_card_code=[ord(x) for x in self.profile_card_code], - nick_name_size=len(self.nick_name) * 2, - nick_name=[ord(x) for x in self.nick_name], - rank_num=0, - setting_title_id=0, - skill_id=0, - hero_log_hero_log_id=0, - hero_log_log_level=0, - hero_log_awakening_stage=0, - hero_log_property1_property_id=0, - hero_log_property1_value1=0, - hero_log_property1_value2=0, - hero_log_property2_property_id=0, - hero_log_property2_value1=0, - hero_log_property2_value2=0, - hero_log_property3_property_id=0, - hero_log_property3_value1=0, - hero_log_property3_value2=0, - hero_log_property4_property_id=0, - hero_log_property4_value1=0, - hero_log_property4_value2=0, - main_weapon_equipment_id=0, - main_weapon_enhancement_value=0, - main_weapon_awakening_stage=0, - main_weapon_property1_property_id=0, - main_weapon_property1_value1=0, - main_weapon_property1_value2=0, - main_weapon_property2_property_id=0, - main_weapon_property2_value1=0, - main_weapon_property2_value2=0, - main_weapon_property3_property_id=0, - main_weapon_property3_value1=0, - main_weapon_property3_value2=0, - main_weapon_property4_property_id=0, - main_weapon_property4_value1=0, - main_weapon_property4_value2=0, - sub_equipment_equipment_id=0, - sub_equipment_enhancement_value=0, - sub_equipment_awakening_stage=0, - sub_equipment_property1_property_id=0, - sub_equipment_property1_value1=0, - sub_equipment_property1_value2=0, - sub_equipment_property2_property_id=0, - sub_equipment_property2_value1=0, - sub_equipment_property2_value2=0, - sub_equipment_property3_property_id=0, - sub_equipment_property3_value1=0, - sub_equipment_property3_value2=0, - sub_equipment_property4_property_id=0, - sub_equipment_property4_value1=0, - sub_equipment_property4_value2=0, - holographic_flag=0, - - )) - - self.length = len(resp_data) - return super().make() + resp_data - -class SaoGetResourcePathInfoRequest(SaoBaseRequest): - def __init__(self, header: SaoRequestHeader, data: bytes) -> None: - super().__init__(header, data) + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.profile_card_data) class SaoGetResourcePathInfoResponse(SaoBaseResponse): - def __init__(self, cmd) -> None: - super().__init__(cmd) + def __init__(self, base_url: str) -> None: + super().__init__(GameconnectCmd.GET_RESOURCE_PATH_INFO_RESPONSE) self.result = 1 - self.resource_base_url = "http://localhost:9000/SDEW/100/" - self.gasha_base_dir = "a" - self.ad_base_dir = "b" - self.event_base_dir = "c" + self.resource_base_url = base_url + self.gasha_base_dir = "gasha" + self.ad_base_dir = "ad" + self.event_base_dir = "event" def make(self) -> bytes: - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "resource_base_url_size" / Int32ub, # big endian - "resource_base_url" / Int16ul[len(self.resource_base_url)], - "gasha_base_dir_size" / Int32ub, # big endian - "gasha_base_dir" / Int16ul[len(self.gasha_base_dir)], - "ad_base_dir_size" / Int32ub, # big endian - "ad_base_dir" / Int16ul[len(self.ad_base_dir)], - "event_base_dir_size" / Int32ub, # big endian - "event_base_dir" / Int16ul[len(self.event_base_dir)], - ) + return super().make() \ + + encode_byte(self.result) \ + + encode_str(self.resource_base_url) \ + + encode_str(self.gasha_base_dir) \ + + encode_str(self.ad_base_dir) \ + + encode_str(self.event_base_dir) - resp_data = resp_struct.build(dict( - result=self.result, - resource_base_url_size=len(self.resource_base_url) * 2, - resource_base_url=[ord(x) for x in self.resource_base_url], - gasha_base_dir_size=len(self.gasha_base_dir) * 2, - gasha_base_dir=[ord(x) for x in self.gasha_base_dir], - ad_base_dir_size=len(self.ad_base_dir) * 2, - ad_base_dir=[ord(x) for x in self.ad_base_dir], - event_base_dir_size=len(self.event_base_dir) * 2, - event_base_dir=[ord(x) for x in self.event_base_dir], - )) +class SaoValidationErrorNotificationRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.user_id, new_off = decode_str(data, off) + off += new_off - self.length = len(resp_data) - return super().make() + resp_data + self.cabinet_type = decode_byte(data, off) + off += BYTE_OFF + + self.net_id, new_off = decode_str(data, off) + off += new_off + + self.place_id, new_off = decode_str(data, off) + off += new_off + + self.store_id, new_off = decode_str(data, off) + off += new_off + + self.store_name, new_off = decode_str(data, off) + off += new_off + + self.serial_no, new_off = decode_str(data, off) + off += new_off + + self.send_protocol_name, new_off = decode_str(data, off) + off += new_off + + self.send_data_to_fraud_value, new_off = decode_str(data, off) + off += new_off + + self.send_data_to_modification_value, new_off = decode_str(data, off) + off += new_off + +class SaoGetBeginnerMissionUserDataRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.beginner_mission_id = decode_int(data, off) + off += INT_OFF + +class SaoGetBeginnerMissionUserDataResponse(SaoBaseResponse): + def __init__(self) -> None: + super().__init__(GameconnectCmd.GET_BEGINNER_MISSION_USER_DATA_RESPONSE) + self.result = 1 + self.data: List[BeginnerMissionUserData] = [BeginnerMissionUserData.from_args(datetime.now())] + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data) + +class SaoMatchingErrorNotificationRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.cabinet_type = decode_byte(data, off) + off += BYTE_OFF + + self.net_id, new_off = decode_str(data, off) + off += new_off + + self.place_id, new_off = decode_str(data, off) + off += new_off + + self.store_id, new_off = decode_str(data, off) + off += new_off + + self.store_name, new_off = decode_str(data, off) + off += new_off + + self.serial_no, new_off = decode_str(data, off) + off += new_off + + self.matching_error_data_list: List[MatchingErrorData] + self.matching_error_data_list, new_off = decode_arr_cls(data, off, MatchingErrorData) + +class SaoPowerCuttingReturnNotification(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.cabinet_type = decode_byte(data, off) + off += BYTE_OFF + + self.net_id, new_off = decode_str(data, off) + off += new_off + + self.place_id, new_off = decode_str(data, off) + off += new_off + + self.store_id, new_off = decode_str(data, off) + off += new_off + + self.store_name, new_off = decode_str(data, off) + off += new_off + + self.serial_no, new_off = decode_str(data, off) + off += new_off + + self.last_act_type = decode_byte(data, off) + off += BYTE_OFF + + self.remaining_ticket_num = decode_short(data, off) + off += SHORT_OFF + + self.remaining_credit_num = decode_short(data, off) + off += SHORT_OFF class SaoEpisodePlayStartRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: super().__init__(header, data) off = 0 - ticket_id = decode_str(data, off) - self.ticket_id = ticket_id[0] - off += ticket_id[1] + self.ticket_id, new_off = decode_str(data, off) + off += new_off - user_id = decode_str(data, off) - self.user_id = user_id[0] - off += user_id[1] + self.user_id, new_off = decode_str(data, off) + off += new_off self.episode_id = decode_int(data, off) off += INT_OFF @@ -1500,183 +927,60 @@ class SaoEpisodePlayStartRequest(SaoBaseRequest): self.play_mode = decode_byte(data, off) off += BYTE_OFF - self.play_start_request_data_count = decode_int(data, off) - off += INT_OFF + self.play_start_request_data: List[QuestScenePlayStartRequestData] = [] + self.play_start_request_data, new_off = decode_arr_cls(data, off, QuestScenePlayStartRequestData) + off += new_off - self.play_start_request_data: List[PlayStartRequestData] = [] - for _ in range(self.play_start_request_data_count): - tmp = PlayStartRequestData(data, off) - self.play_start_request_data.append(tmp) - off += tmp.get_size() - - self.multi_play_start_request_data_count = decode_int(data, off) - off += INT_OFF - - self.multi_play_start_request_data: List[MultiPlayStartRequestData] = [] - for _ in range(self.multi_play_start_request_data_count): - tmp = MultiPlayStartRequestData(data, off) - off += tmp.get_size() - self.multi_play_start_request_data.append(tmp) + self.multi_play_start_request_data: List[QuestSceneMultiPlayStartRequestData] = [] + self.multi_play_start_request_data, new_off = decode_arr_cls(data, off, QuestSceneMultiPlayStartRequestData) + off += new_off class SaoEpisodePlayStartResponse(SaoBaseResponse): - def __init__(self, cmd, profile_data) -> None: - super().__init__(cmd) + def __init__(self) -> None: + super().__init__(GameconnectCmd.EPISODE_PLAY_START_RESPONSE) self.result = 1 - self.play_start_response_data_size = 1 # Number of arrays (minimum 1 mandatory) - self.multi_play_start_response_data_size = 0 # Number of arrays (set 0 due to single play) + self.play_start_response_data: List[QuestScenePlayStartResponseData] = [] + self.multi_play_start_response_data: List[QuestSceneMultiPlayStartResponseData] = [] - self.appearance_player_trace_data_list_size = 1 - - self.user_quest_scene_player_trace_id = "1003" - self.nick_name = profile_data["nick_name"] - def make(self) -> bytes: - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "play_start_response_data_size" / Int32ub, - "multi_play_start_response_data_size" / Int32ub, - - "appearance_player_trace_data_list_size" / Int32ub, - - "user_quest_scene_player_trace_id_size" / Int32ub, # big endian - "user_quest_scene_player_trace_id" / Int16ul[len(self.user_quest_scene_player_trace_id)], - "nick_name_size" / Int32ub, # big endian - "nick_name" / Int16ul[len(self.nick_name)], - ) - - resp_data = resp_struct.build(dict( - result=self.result, - play_start_response_data_size=self.play_start_response_data_size, - multi_play_start_response_data_size=self.multi_play_start_response_data_size, - - appearance_player_trace_data_list_size=self.appearance_player_trace_data_list_size, - - user_quest_scene_player_trace_id_size=len(self.user_quest_scene_player_trace_id) * 2, - user_quest_scene_player_trace_id=[ord(x) for x in self.user_quest_scene_player_trace_id], - nick_name_size=len(self.nick_name) * 2, - nick_name=[ord(x) for x in self.nick_name], - )) - - self.length = len(resp_data) - return super().make() + resp_data + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.play_start_response_data) \ + + encode_arr_cls(self.multi_play_start_response_data) class SaoEpisodePlayEndRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: super().__init__(header, data) off = 0 - ticket_id = decode_str(data, off) - self.ticket_id = ticket_id[0] - off += ticket_id[1] + self.ticket_id, new_off = decode_str(data, off) + off += new_off - user_id = decode_str(data, off) - self.user_id = user_id[0] - off += user_id[1] + self.user_id, new_off = decode_str(data, off) + off += new_off self.episode_id = decode_int(data, off) off += INT_OFF - self.play_end_request_data_count = decode_int(data, off) - off += INT_OFF + self.play_end_request_data: List[QuestScenePlayEndRequestData] = [] + self.play_end_request_data, new_off = decode_arr_cls(data, off, QuestScenePlayEndRequestData) + off += new_off - self.play_end_request_data_list: List[PlayEndRequestData] = [] - for _ in range(self.play_end_request_data_count): - tmp = PlayEndRequestData(data, off) - off += tmp.get_size() - self.play_end_request_data_list.append(tmp) - - self.multi_play_end_request_data_count = decode_int(data, off) - off += INT_OFF - - self.multi_play_end_request_data_list: List[MultiPlayEndRequestData] = [] - for _ in range(self.multi_play_end_request_data_count): - tmp = MultiPlayEndRequestData(data, off) - off += tmp.get_size() - self.multi_play_end_request_data_list.append(tmp) + self.multi_play_end_request_data: List[QuestSceneMultiPlayEndRequestData] = [] + self.multi_play_end_request_data, new_off = decode_arr_cls(data, off, QuestSceneMultiPlayEndRequestData) + off += new_off class SaoEpisodePlayEndResponse(SaoBaseResponse): - def __init__(self, cmd) -> None: - super().__init__(cmd) + def __init__(self) -> None: + super().__init__(GameconnectCmd.EPISODE_PLAY_END_RESPONSE) self.result = 1 - self.play_end_response_data_size = 1 # Number of arrays - self.multi_play_end_response_data_size = 1 # Unused on solo play + self.play_end_response_data: List[QuestScenePlayEndResponseData] = [QuestScenePlayEndResponseData.from_args()] + self.multi_play_end_response_data: List[QuestSceneMultiPlayEndResponseData] = [QuestSceneMultiPlayEndResponseData.from_args()] - self.dummy_1 = 0 - self.dummy_2 = 0 - self.dummy_3 = 0 - - self.rarity_up_occurrence_flag = 0 - self.adventure_ex_area_occurrences_flag = 0 - self.ex_bonus_data_list_size = 1 # Number of arrays - self.play_end_player_trace_reward_data_list_size = 0 # Number of arrays - - self.ex_bonus_table_id = 0 # ExBonusTable.csv values, dont care for now - self.achievement_status = 1 - - self.common_reward_data_size = 1 # Number of arrays - - self.common_reward_type = 0 # dummy values from 2,101000000,1 from RewardTable.csv - self.common_reward_id = 0 - self.common_reward_num = 0 - def make(self) -> bytes: - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "play_end_response_data_size" / Int32ub, # big endian - - "rarity_up_occurrence_flag" / Int8ul, # result is either 0 or 1 - "adventure_ex_area_occurrences_flag" / Int8ul, # result is either 0 or 1 - "ex_bonus_data_list_size" / Int32ub, # big endian - "play_end_player_trace_reward_data_list_size" / Int32ub, # big endian - - # ex_bonus_data_list - "ex_bonus_table_id" / Int32ub, - "achievement_status" / Int8ul, # result is either 0 or 1 - - # play_end_player_trace_reward_data_list - "common_reward_data_size" / Int32ub, - - # common_reward_data - "common_reward_type" / Int16ub, # short - "common_reward_id" / Int32ub, - "common_reward_num" / Int32ub, - - "multi_play_end_response_data_size" / Int32ub, # big endian - - # multi_play_end_response_data - "dummy_1" / Int8ul, # result is either 0 or 1 - "dummy_2" / Int8ul, # result is either 0 or 1 - "dummy_3" / Int8ul, # result is either 0 or 1 - ) - - resp_data = resp_struct.build(dict( - result=self.result, - play_end_response_data_size=self.play_end_response_data_size, - - rarity_up_occurrence_flag=self.rarity_up_occurrence_flag, - adventure_ex_area_occurrences_flag=self.adventure_ex_area_occurrences_flag, - ex_bonus_data_list_size=self.ex_bonus_data_list_size, - play_end_player_trace_reward_data_list_size=self.play_end_player_trace_reward_data_list_size, - - ex_bonus_table_id=self.ex_bonus_table_id, - achievement_status=self.achievement_status, - - common_reward_data_size=self.common_reward_data_size, - - common_reward_type=self.common_reward_type, - common_reward_id=self.common_reward_id, - common_reward_num=self.common_reward_num, - - multi_play_end_response_data_size=self.multi_play_end_response_data_size, - - dummy_1=self.dummy_1, - dummy_2=self.dummy_2, - dummy_3=self.dummy_3, - )) - - self.length = len(resp_data) - return super().make() + resp_data + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.play_end_response_data) \ + + encode_arr_cls(self.multi_play_end_response_data) class SaoTrialTowerPlayStartRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: @@ -1714,160 +1018,56 @@ class SaoTrialTowerPlayStartRequest(SaoBaseRequest): off += tmp.get_size() self.multi_play_start_request_data.append(tmp) +class SaoTrialTowerPlayStartResponse(SaoBaseResponse): + def __init__(self, sesh_id: int, nickname: str) -> None: + super().__init__(GameconnectCmd.TRIAL_TOWER_PLAY_START_RESPONSE) + self.result = 1 + self.play_start_response_data: List[QuestScenePlayStartResponseData] = [QuestScenePlayStartResponseData.from_args(sesh_id, nickname)] + self.multi_play_start_response_data: List[QuestSceneMultiPlayStartResponseData] = [QuestSceneMultiPlayStartResponseData.from_args()] + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.play_start_response_data) \ + + encode_arr_cls(self.multi_play_start_response_data) + class SaoTrialTowerPlayEndRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: super().__init__(header, data) off = 0 - ticket_id = decode_str(data, off) - self.ticket_id = ticket_id[0] - off += ticket_id[1] + self.ticket_id, new_off = decode_str(data, off) + off += new_off - user_id = decode_str(data, off) - self.user_id = user_id[0] - off += user_id[1] + self.user_id, new_off = decode_str(data, off) + off += new_off self.trial_tower_id = decode_int(data, off) off += INT_OFF - self.play_end_request_data_count = decode_int(data, off) - off += INT_OFF + self.play_end_request_data: List[QuestScenePlayEndRequestData] = [] + self.play_end_request_data, new_off = decode_arr_cls(data, off, QuestScenePlayEndRequestData) + off += new_off - self.play_end_request_data_list: List[PlayEndRequestData] = [] - for _ in range(self.play_end_request_data_count): - tmp = PlayEndRequestData(data, off) - off += tmp.get_size() - self.play_end_request_data_list.append(tmp) - - self.multi_play_end_request_data_count = decode_int(data, off) - off += INT_OFF - - self.multi_play_end_request_data_list: List[MultiPlayEndRequestData] = [] - for _ in range(self.multi_play_end_request_data_count): - tmp = MultiPlayEndRequestData(data, off) - off += tmp.get_size() - self.multi_play_end_request_data_list.append(tmp) + self.multi_play_end_request_data: List[QuestSceneMultiPlayEndRequestData] + self.multi_play_end_request_data, new_off = decode_arr_cls(data, off, QuestSceneMultiPlayEndRequestData) + off += new_off class SaoTrialTowerPlayEndResponse(SaoBaseResponse): - def __init__(self, cmd) -> None: - super().__init__(cmd) + def __init__(self) -> None: + super().__init__(GameconnectCmd.TRIAL_TOWER_PLAY_END_RESPONSE) self.result = 1 - self.play_end_response_data_size = 1 # Number of arrays - self.multi_play_end_response_data_size = 1 # Unused on solo play - self.trial_tower_play_end_updated_notification_data_size = 1 # Number of arrays - self.treasure_hunt_play_end_response_data_size = 1 # Number of arrays + self.play_end_response_data: List[QuestScenePlayEndResponseData] = [QuestScenePlayEndResponseData.from_args()] + self.multi_play_end_response_data: List[QuestSceneMultiPlayEndResponseData] = [QuestSceneMultiPlayEndResponseData.from_args()] + self.trial_tower_play_end_updated_notification_data: List[QuestTrialTowerPlayEndUpdatedNotificationData] = [QuestTrialTowerPlayEndUpdatedNotificationData.from_args()] + self.treasure_hunt_play_end_response_data: List[QuestTreasureHuntPlayEndResponseData] = [QuestTreasureHuntPlayEndResponseData.from_args()] - self.dummy_1 = 0 - self.dummy_2 = 0 - self.dummy_3 = 0 - - self.rarity_up_occurrence_flag = 0 - self.adventure_ex_area_occurrences_flag = 0 - self.ex_bonus_data_list_size = 1 # Number of arrays - self.play_end_player_trace_reward_data_list_size = 0 # Number of arrays - - self.ex_bonus_table_id = 0 # ExBonusTable.csv values, dont care for now - self.achievement_status = 1 - - self.common_reward_data_size = 1 # Number of arrays - - self.common_reward_type = 0 # dummy values from 2,101000000,1 from RewardTable.csv - self.common_reward_id = 0 - self.common_reward_num = 0 - - self.store_best_score_clear_time_flag = 0 - self.store_best_score_combo_num_flag = 0 - self.store_best_score_total_damage_flag = 0 - self.store_best_score_concurrent_destroying_num_flag = 0 - self.store_reaching_trial_tower_rank = 0 - - self.get_event_point = 0 - self.total_event_point = 0 - def make(self) -> bytes: - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "play_end_response_data_size" / Int32ub, # big endian - - "rarity_up_occurrence_flag" / Int8ul, # result is either 0 or 1 - "adventure_ex_area_occurrences_flag" / Int8ul, # result is either 0 or 1 - "ex_bonus_data_list_size" / Int32ub, # big endian - "play_end_player_trace_reward_data_list_size" / Int32ub, # big endian - - # ex_bonus_data_list - "ex_bonus_table_id" / Int32ub, - "achievement_status" / Int8ul, # result is either 0 or 1 - - # play_end_player_trace_reward_data_list - "common_reward_data_size" / Int32ub, - - # common_reward_data - "common_reward_type" / Int16ub, # short - "common_reward_id" / Int32ub, - "common_reward_num" / Int32ub, - - "multi_play_end_response_data_size" / Int32ub, # big endian - - # multi_play_end_response_data - "dummy_1" / Int8ul, # result is either 0 or 1 - "dummy_2" / Int8ul, # result is either 0 or 1 - "dummy_3" / Int8ul, # result is either 0 or 1 - - "trial_tower_play_end_updated_notification_data_size" / Int32ub, # big endian - - #trial_tower_play_end_updated_notification_data - "store_best_score_clear_time_flag" / Int8ul, # result is either 0 or 1 - "store_best_score_combo_num_flag" / Int8ul, # result is either 0 or 1 - "store_best_score_total_damage_flag" / Int8ul, # result is either 0 or 1 - "store_best_score_concurrent_destroying_num_flag" / Int8ul, # result is either 0 or 1 - "store_reaching_trial_tower_rank" / Int32ub, - - "treasure_hunt_play_end_response_data_size" / Int32ub, # big endian - - #treasure_hunt_play_end_response_data - "get_event_point" / Int32ub, - "total_event_point" / Int32ub, - ) - - resp_data = resp_struct.build(dict( - result=self.result, - play_end_response_data_size=self.play_end_response_data_size, - - rarity_up_occurrence_flag=self.rarity_up_occurrence_flag, - adventure_ex_area_occurrences_flag=self.adventure_ex_area_occurrences_flag, - ex_bonus_data_list_size=self.ex_bonus_data_list_size, - play_end_player_trace_reward_data_list_size=self.play_end_player_trace_reward_data_list_size, - - ex_bonus_table_id=self.ex_bonus_table_id, - achievement_status=self.achievement_status, - - common_reward_data_size=self.common_reward_data_size, - - common_reward_type=self.common_reward_type, - common_reward_id=self.common_reward_id, - common_reward_num=self.common_reward_num, - - multi_play_end_response_data_size=self.multi_play_end_response_data_size, - - dummy_1=self.dummy_1, - dummy_2=self.dummy_2, - dummy_3=self.dummy_3, - - trial_tower_play_end_updated_notification_data_size=self.trial_tower_play_end_updated_notification_data_size, - store_best_score_clear_time_flag=self.store_best_score_clear_time_flag, - store_best_score_combo_num_flag=self.store_best_score_combo_num_flag, - store_best_score_total_damage_flag=self.store_best_score_total_damage_flag, - store_best_score_concurrent_destroying_num_flag=self.store_best_score_concurrent_destroying_num_flag, - store_reaching_trial_tower_rank=self.store_reaching_trial_tower_rank, - - treasure_hunt_play_end_response_data_size=self.treasure_hunt_play_end_response_data_size, - - get_event_point=self.get_event_point, - total_event_point=self.total_event_point, - )) - - self.length = len(resp_data) - return super().make() + resp_data + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.play_end_response_data) \ + + encode_arr_cls(self.multi_play_end_response_data) \ + + encode_arr_cls(self.trial_tower_play_end_updated_notification_data) \ + + encode_arr_cls(self.treasure_hunt_play_end_response_data) class SaoEpisodePlayEndUnanalyzedLogFixedRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: @@ -1888,1150 +1088,597 @@ class SaoEpisodePlayEndUnanalyzedLogFixedRequest(SaoBaseRequest): off += BYTE_OFF class SaoEpisodePlayEndUnanalyzedLogFixedResponse(SaoBaseResponse): - def __init__(self, cmd, end_session_data) -> None: - super().__init__(cmd) + def __init__(self) -> None: + super().__init__(GameconnectCmd.EPISODE_PLAY_END_UNANALYZED_LOG_FIXED_RESPONSE) self.result = 1 + self.play_end_unanalyzed_log_reward_data_list: List[QuestScenePlayEndUnanalyzedLogRewardData] = [] - self.unanalyzed_log_grade_id = [] - - self.common_reward_type = [] - self.common_reward_id = [] - self.common_reward_num = 1 - - for x in range(len(end_session_data)): - self.common_reward_id.append(end_session_data[x]) - - with open('titles/sao/data/RewardTable.csv', 'r') as f: - keys_unanalyzed = next(f).strip().split(',') - data_unanalyzed = list(DictReader(f, fieldnames=keys_unanalyzed)) - - for i in range(len(data_unanalyzed)): - if int(data_unanalyzed[i]["CommonRewardId"]) == int(end_session_data[x]): - self.unanalyzed_log_grade_id.append(int(data_unanalyzed[i]["UnanalyzedLogGradeId"])) - self.common_reward_type.append(int(data_unanalyzed[i]["CommonRewardType"])) - break - - self.unanalyzed_log_grade_id = list(map(int,self.unanalyzed_log_grade_id)) #int - self.common_reward_type = list(map(int,self.common_reward_type)) #int - self.common_reward_id = list(map(int,self.common_reward_id)) #int - def make(self) -> bytes: - #new stuff - common_reward_data_struct = Struct( - "common_reward_type" / Int16ub, - "common_reward_id" / Int32ub, - "common_reward_num" / Int32ub, - ) - - play_end_unanalyzed_log_reward_data_list_struct = Struct( - "unanalyzed_log_grade_id" / Int32ub, - "common_reward_data_size" / Rebuild(Int32ub, len_(this.common_reward_data)), # big endian - "common_reward_data" / Array(this.common_reward_data_size, common_reward_data_struct), - ) - - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "play_end_unanalyzed_log_reward_data_list_size" / Rebuild(Int32ub, len_(this.play_end_unanalyzed_log_reward_data_list)), # big endian - "play_end_unanalyzed_log_reward_data_list" / Array(this.play_end_unanalyzed_log_reward_data_list_size, play_end_unanalyzed_log_reward_data_list_struct), - ) - - resp_data = resp_struct.parse(resp_struct.build(dict( - result=self.result, - play_end_unanalyzed_log_reward_data_list_size=0, - play_end_unanalyzed_log_reward_data_list=[], - ))) - - for i in range(len(self.common_reward_id)): - reward_resp_data = dict( - unanalyzed_log_grade_id=self.unanalyzed_log_grade_id[i], - common_reward_data_size=0, - common_reward_data=[], - ) - - reward_resp_data["common_reward_data"].append(dict( - common_reward_type=self.common_reward_type[i], - common_reward_id=self.common_reward_id[i], - common_reward_num=self.common_reward_num, - )) - - resp_data.play_end_unanalyzed_log_reward_data_list.append(reward_resp_data) - - resp_data["play_end_unanalyzed_log_reward_data_list_size"] = len(resp_data.play_end_unanalyzed_log_reward_data_list) - for i in range(len(resp_data.play_end_unanalyzed_log_reward_data_list)): - resp_data.play_end_unanalyzed_log_reward_data_list[i]["common_reward_data_size"] = len(resp_data.play_end_unanalyzed_log_reward_data_list[i]["common_reward_data"]) - - # finally, rebuild the resp_data - resp_data = resp_struct.build(resp_data) - - self.length = len(resp_data) - return super().make() + resp_data - -class SaoGetQuestSceneUserDataListRequest(SaoBaseRequest): - def __init__(self, header: SaoRequestHeader, data: bytes) -> None: - super().__init__(header, data) - self.user_id = decode_str(data, 0)[0] + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.play_end_unanalyzed_log_reward_data_list) class SaoGetQuestSceneUserDataListResponse(SaoBaseResponse): - def __init__(self, cmd, quest_data) -> None: - super().__init__(cmd) - self.length = None + def __init__(self) -> None: + super().__init__(GameconnectCmd.GET_QUEST_SCENE_USER_DATA_LIST_RESPONSE) self.result = 1 + self.quest_scene_user_data_list: List[QuestSceneUserData] = [] - # quest_scene_user_data_list_size - self.quest_type = [] - self.quest_scene_id = [] - self.clear_flag = [] - - # quest_scene_best_score_user_data - self.clear_time = [] - self.combo_num = [] - self.total_damage = [] #string - self.concurrent_destroying_num = [] - - for i in range(len(quest_data)): - self.quest_type.append(1) - self.quest_scene_id.append(quest_data[i][2]) - self.clear_flag.append(int(quest_data[i][3])) - - self.clear_time.append(quest_data[i][4]) - self.combo_num.append(quest_data[i][5]) - self.total_damage.append(0) #totally absurd but Int16ul[1] is a big problem due to different lenghts... - self.concurrent_destroying_num.append(quest_data[i][7]) - - # quest_scene_ex_bonus_user_data_list - self.achievement_flag = [1,1,1] - self.ex_bonus_table_id = [1,2,3] - - - self.quest_type = list(map(int,self.quest_type)) #int - self.quest_scene_id = list(map(int,self.quest_scene_id)) #int - self.clear_flag = list(map(int,self.clear_flag)) #int - self.clear_time = list(map(int,self.clear_time)) #int - self.combo_num = list(map(int,self.combo_num)) #int - self.total_damage = list(map(str,self.total_damage)) #string - self.concurrent_destroying_num = list(map(int,self.combo_num)) #int - def make(self) -> bytes: - #new stuff - quest_scene_ex_bonus_user_data_list_struct = Struct( - "ex_bonus_table_id" / Int32ub, # big endian - "achievement_flag" / Int8ul, # result is either 0 or 1 - ) - - quest_scene_best_score_user_data_struct = Struct( - "clear_time" / Int32ub, # big endian - "combo_num" / Int32ub, # big endian - "total_damage_size" / Int32ub, # big endian - "total_damage" / Int16ul[1], - "concurrent_destroying_num" / Int16ub, - ) - - quest_scene_user_data_list_struct = Struct( - "quest_type" / Int8ul, # result is either 0 or 1 - "quest_scene_id" / Int16ub, #short - "clear_flag" / Int8ul, # result is either 0 or 1 - "quest_scene_best_score_user_data_size" / Rebuild(Int32ub, len_(this.quest_scene_best_score_user_data)), # big endian - "quest_scene_best_score_user_data" / Array(this.quest_scene_best_score_user_data_size, quest_scene_best_score_user_data_struct), - "quest_scene_ex_bonus_user_data_list_size" / Rebuild(Int32ub, len_(this.quest_scene_ex_bonus_user_data_list)), # big endian - "quest_scene_ex_bonus_user_data_list" / Array(this.quest_scene_ex_bonus_user_data_list_size, quest_scene_ex_bonus_user_data_list_struct), - ) - - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "quest_scene_user_data_list_size" / Rebuild(Int32ub, len_(this.quest_scene_user_data_list)), # big endian - "quest_scene_user_data_list" / Array(this.quest_scene_user_data_list_size, quest_scene_user_data_list_struct), - ) - - resp_data = resp_struct.parse(resp_struct.build(dict( - result=self.result, - quest_scene_user_data_list_size=0, - quest_scene_user_data_list=[], - ))) - - for i in range(len(self.quest_scene_id)): - quest_resp_data = dict( - quest_type=self.quest_type[i], - quest_scene_id=self.quest_scene_id[i], - clear_flag=self.clear_flag[i], - - quest_scene_best_score_user_data_size=0, - quest_scene_best_score_user_data=[], - quest_scene_ex_bonus_user_data_list_size=0, - quest_scene_ex_bonus_user_data_list=[], - ) - - quest_resp_data["quest_scene_best_score_user_data"].append(dict( - clear_time=self.clear_time[i], - combo_num=self.combo_num[i], - total_damage_size=len(self.total_damage[i]) * 2, - total_damage=[ord(x) for x in self.total_damage[i]], - concurrent_destroying_num=self.concurrent_destroying_num[i], - )) - - resp_data.quest_scene_user_data_list.append(quest_resp_data) - - resp_data["quest_scene_user_data_list_size"] = len(resp_data.quest_scene_user_data_list) - for i in range(len(resp_data.quest_scene_user_data_list)): - resp_data.quest_scene_user_data_list[i]["quest_scene_best_score_user_data_size"] = len(resp_data.quest_scene_user_data_list[i]["quest_scene_best_score_user_data"]) - - # finally, rebuild the resp_data - resp_data = resp_struct.build(resp_data) - - self.length = len(resp_data) - return super().make() + resp_data + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.quest_scene_user_data_list) class SaoCheckYuiMedalGetConditionRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: super().__init__(header, data) + off = 0 + self.ticket_id, new_off = decode_str(data, off) + off += new_off + self.user_id, new_off = decode_str(data, off) + off += new_off class SaoCheckYuiMedalGetConditionResponse(SaoBaseResponse): - def __init__(self, cmd) -> None: - super().__init__(cmd) + def __init__(self, elapsed_days: int = 0, get_num: int = 0) -> None: + super().__init__(GameconnectCmd.CHECK_YUI_MEDAL_GET_CONDITION_RESPONSE) self.result = 1 - self.get_flag = 1 - self.elapsed_days = 0 - self.get_yui_medal_num = 0 + self.get_flag = int(get_num > 0) + self.elapsed_days = elapsed_days + self.get_yui_medal_num = get_num def make(self) -> bytes: - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "get_flag" / Int8ul, # result is either 0 or 1 - "elapsed_days" / Int16ub, #short - "get_yui_medal_num" / Int16ub, #short - ) - - resp_data = resp_struct.build(dict( - result=self.result, - get_flag=self.get_flag, - elapsed_days=self.elapsed_days, - get_yui_medal_num=self.get_yui_medal_num, - )) - - self.length = len(resp_data) - return super().make() + resp_data + return super().make() \ + + encode_byte(self.result) \ + + encode_byte(self.get_flag) \ + + encode_short(self.elapsed_days) \ + + encode_short(self.get_yui_medal_num) \ class SaoGetYuiMedalBonusUserDataRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: super().__init__(header, data) + off = 0 + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.yui_medal_bonus_id = decode_int(data, off) + off += INT_OFF class SaoGetYuiMedalBonusUserDataResponse(SaoBaseResponse): - def __init__(self, cmd) -> None: - super().__init__(cmd) + def __init__(self, elapsed_days: int = 0, loop_num: int = 0) -> None: + super().__init__(GameconnectCmd.GET_YUI_MEDAL_BONUS_USER_DATA_RESPONSE) self.result = 1 - self.data_size = 1 # number of arrays - - self.elapsed_days = 1 - self.loop_num = 1 - self.last_check_date = "20230520193000" - self.last_get_date = "20230520193000" + self.data: List[YuiMedalBonusUserData] = [YuiMedalBonusUserData.from_args(elapsed_days, loop_num)] def make(self) -> bytes: - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "data_size" / Int32ub, # big endian - - "elapsed_days" / Int32ub, # big endian - "loop_num" / Int32ub, # big endian - "last_check_date_size" / Int32ub, # big endian - "last_check_date" / Int16ul[len(self.last_check_date)], - "last_get_date_size" / Int32ub, # big endian - "last_get_date" / Int16ul[len(self.last_get_date)], - ) - - resp_data = resp_struct.build(dict( - result=self.result, - data_size=self.data_size, - - elapsed_days=self.elapsed_days, - loop_num=self.loop_num, - last_check_date_size=len(self.last_check_date) * 2, - last_check_date=[ord(x) for x in self.last_check_date], - last_get_date_size=len(self.last_get_date) * 2, - last_get_date=[ord(x) for x in self.last_get_date], - - )) - - self.length = len(resp_data) - return super().make() + resp_data + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data) class SaoCheckProfileCardUsedRewardRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: super().__init__(header, data) + off = 0 + self.ticket_id, new_off = decode_str(data, off) + off += new_off + self.user_id, new_off = decode_str(data, off) + off += new_off class SaoCheckProfileCardUsedRewardResponse(SaoBaseResponse): - def __init__(self, cmd) -> None: - super().__init__(cmd) + def __init__(self) -> None: + super().__init__(GameconnectCmd.CHECK_PROFILE_CARD_USED_REWARD_RESPONSE) self.result = 1 - self.get_flag = 1 + self.get_flag = 0 self.used_num = 0 - self.get_vp = 1 + self.get_vp = 0 def make(self) -> bytes: - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "get_flag" / Int8ul, # result is either 0 or 1 - "used_num" / Int32ub, # big endian - "get_vp" / Int32ub, # big endian - ) + return super().make() \ + + encode_byte(self.result) \ + + encode_byte(self.get_flag) \ + + encode_int(self.used_num) \ + + encode_int(self.get_vp) - resp_data = resp_struct.build(dict( - result=self.result, - get_flag=self.get_flag, - used_num=self.used_num, - get_vp=self.get_vp, - - )) +class SaoDisposalResourceRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.ticket_id, new_off = decode_str(data, off) + off += new_off - self.length = len(resp_data) - return super().make() + resp_data + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.disposal_common_reward_user_data_list: List[CommonRewardUserData] = [] + self.disposal_common_reward_user_data_list, new_off = decode_arr_cls(data, off, CommonRewardUserData) + off += new_off + +class SaoDisposalResourceResponse(SaoBaseResponse): + def __init__(self, get_col: int = 0) -> None: + super().__init__(GameconnectCmd.DISPOSAL_RESOURCE_RESPONSE) + self.result = 1 + self.get_col = get_col + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_int(self.get_col) class SaoSynthesizeEnhancementHeroLogRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: super().__init__(header, data) off = 0 - ticket_id = decode_str(data, off) - self.ticket_id = ticket_id[0] - off += ticket_id[1] + self.ticket_id, new_off = decode_str(data, off) + off += new_off - user_id = decode_str(data, off) - self.user_id = user_id[0] - off += user_id[1] + self.user_id, new_off = decode_str(data, off) + off += new_off - origin_user_hero_log_id = decode_str(data, off) - self.origin_user_hero_log_id = origin_user_hero_log_id[0] - off += origin_user_hero_log_id[1] + self.origin_user_hero_log_id, new_off = decode_str(data, off) + off += new_off - self.material_common_reward_user_data_list: List[MaterialCommonRewardUserData] = [] - - self.material_common_reward_user_data_count = decode_int(data, off) - off += INT_OFF - - for _ in range(self.material_common_reward_user_data_count): - mat = MaterialCommonRewardUserData(data, off) - off += mat.get_size() - self.material_common_reward_user_data_list.append(mat) + self.material_common_reward_user_data_list: List[CommonRewardUserData] = [] + self.material_common_reward_user_data_list, new_off = decode_arr_cls(data, off, CommonRewardUserData) + off += new_off class SaoSynthesizeEnhancementHeroLogResponse(SaoBaseResponse): - def __init__(self, cmd, hero_data) -> None: - super().__init__(cmd) + def __init__(self) -> None: + super().__init__(GameconnectCmd.SYNTHESIZE_ENHANCEMENT_HERO_LOG_RESPONSE) self.result = 1 + self.after_hero_log_user_data: List[HeroLogUserData] = [] - # Calculate level based off experience and the CSV list - with open(r'titles/sao/data/HeroLogLevel.csv') as csv_file: - csv_reader = csv.reader(csv_file, delimiter=',') - line_count = 0 - data = [] - rowf = False - for row in csv_reader: - if rowf==False: - rowf=True - else: - data.append(row) - - exp = hero_data[4] - - for e in range(0,len(data)): - if exp>=int(data[e][1]) and exp bytes: - #new stuff - - hero_log_user_data_list_struct = Struct( - "user_hero_log_id_size" / Int32ub, # big endian - "user_hero_log_id" / Int16ul[9], #string - "hero_log_id" / Int32ub, #int - "log_level" / Int16ub, #short - "max_log_level_extended_num" / Int16ub, #short - "log_exp" / Int32ub, #int - "possible_awakening_flag" / Int8ul, # result is either 0 or 1 - "awakening_stage" / Int16ub, #short - "awakening_exp" / Int32ub, #int - "skill_slot_correction_value" / Int8ul, # result is either 0 or 1 - "last_set_skill_slot1_skill_id" / Int16ub, #short - "last_set_skill_slot2_skill_id" / Int16ub, #short - "last_set_skill_slot3_skill_id" / Int16ub, #short - "last_set_skill_slot4_skill_id" / Int16ub, #short - "last_set_skill_slot5_skill_id" / Int16ub, #short - "property1_property_id" / Int32ub, - "property1_value1" / Int32ub, - "property1_value2" / Int32ub, - "property2_property_id" / Int32ub, - "property2_value1" / Int32ub, - "property2_value2" / Int32ub, - "property3_property_id" / Int32ub, - "property3_value1" / Int32ub, - "property3_value2" / Int32ub, - "property4_property_id" / Int32ub, - "property4_value1" / Int32ub, - "property4_value2" / Int32ub, - "converted_card_num" / Int16ub, - "shop_purchase_flag" / Int8ul, # result is either 0 or 1 - "protect_flag" / Int8ul, # result is either 0 or 1 - "get_date_size" / Int32ub, # big endian - "get_date" / Int16ul[len(self.get_date)], - ) - - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "hero_log_user_data_list_size" / Rebuild(Int32ub, len_(this.hero_log_user_data_list)), # big endian - "hero_log_user_data_list" / Array(this.hero_log_user_data_list_size, hero_log_user_data_list_struct), - ) - - resp_data = resp_struct.parse(resp_struct.build(dict( - result=self.result, - hero_log_user_data_list_size=0, - hero_log_user_data_list=[], - ))) - - hero_data = dict( - user_hero_log_id_size=len(self.user_hero_log_id) * 2, - user_hero_log_id=[ord(x) for x in self.user_hero_log_id], - hero_log_id=self.hero_log_id, - log_level=self.log_level, - max_log_level_extended_num=self.max_log_level_extended_num, - log_exp=self.log_exp, - possible_awakening_flag=self.possible_awakening_flag, - awakening_stage=self.awakening_stage, - awakening_exp=self.awakening_exp, - skill_slot_correction_value=self.skill_slot_correction_value, - last_set_skill_slot1_skill_id=self.last_set_skill_slot1_skill_id, - last_set_skill_slot2_skill_id=self.last_set_skill_slot2_skill_id, - last_set_skill_slot3_skill_id=self.last_set_skill_slot3_skill_id, - last_set_skill_slot4_skill_id=self.last_set_skill_slot4_skill_id, - last_set_skill_slot5_skill_id=self.last_set_skill_slot5_skill_id, - property1_property_id=self.property1_property_id, - property1_value1=self.property1_value1, - property1_value2=self.property1_value2, - property2_property_id=self.property2_property_id, - property2_value1=self.property2_value1, - property2_value2=self.property2_value2, - property3_property_id=self.property3_property_id, - property3_value1=self.property3_value1, - property3_value2=self.property3_value2, - property4_property_id=self.property4_property_id, - property4_value1=self.property4_value1, - property4_value2=self.property4_value2, - converted_card_num=self.converted_card_num, - shop_purchase_flag=self.shop_purchase_flag, - protect_flag=self.protect_flag, - get_date_size=len(self.get_date) * 2, - get_date=[ord(x) for x in self.get_date], - - ) - - resp_data.hero_log_user_data_list.append(hero_data) - - resp_data["hero_log_user_data_list_size"] = len(resp_data.hero_log_user_data_list) - - # finally, rebuild the resp_data - resp_data = resp_struct.build(resp_data) - - self.length = len(resp_data) - return super().make() + resp_data + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.after_hero_log_user_data) class SaoSynthesizeEnhancementEquipmentRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: super().__init__(header, data) off = 0 - ticket_id = decode_str(data, off) - self.ticket_id = ticket_id[0] - off += ticket_id[1] + self.ticket_id, new_off = decode_str(data, off) + off += new_off - user_id = decode_str(data, off) - self.user_id = user_id[0] - off += user_id[1] - - origin_user_equipment_id = decode_str(data, off) - self.origin_user_equipment_id = origin_user_equipment_id[0] - off += origin_user_equipment_id[1] - - self.material_common_reward_user_data_list: List[MaterialCommonRewardUserData] = [] - - self.material_common_reward_user_data_count = decode_int(data, off) - off += INT_OFF + self.user_id, new_off = decode_str(data, off) + off += new_off - for _ in range(self.material_common_reward_user_data_count): - mat = MaterialCommonRewardUserData(data, off) - off += mat.get_size() - self.material_common_reward_user_data_list.append(mat) + self.origin_user_equipment_id, new_off = decode_str(data, off) + off += new_off + + self.material_common_reward_user_data_list: List[CommonRewardUserData] = [] + self.material_common_reward_user_data_list, new_off = decode_arr_cls(data, off, CommonRewardUserData) + off += new_off class SaoSynthesizeEnhancementEquipmentResponse(SaoBaseResponse): - def __init__(self, cmd, synthesize_equipment_data) -> None: - super().__init__(cmd) + def __init__(self) -> None: + super().__init__(GameconnectCmd.SYNTHESIZE_ENHANCEMENT_EQUIPMENT_RESPONSE) self.result = 1 - equipment_level = 0 + self.after_equipment_user_data: List[EquipmentUserData] = [] - # Calculate level based off experience and the CSV list - with open(r'titles/sao/data/EquipmentLevel.csv') as csv_file: - csv_reader = csv.reader(csv_file, delimiter=',') - line_count = 0 - data = [] - rowf = False - for row in csv_reader: - if rowf==False: - rowf=True - else: - data.append(row) - - exp = synthesize_equipment_data[4] - - for e in range(0,len(data)): - if exp>=int(data[e][1]) and exp bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.after_equipment_user_data) - after_equipment_user_data_struct = Struct( - "user_equipment_id_size" / Int32ub, # big endian - "user_equipment_id" / Int16ul[9], #string - "equipment_id" / Int32ub, #int - "enhancement_value" / Int16ub, #short - "max_enhancement_value_extended_num" / Int16ub, #short - "enhancement_exp" / Int32ub, #int - "possible_awakening_flag" / Int8ul, # result is either 0 or 1 - "awakening_stage" / Int16ub, #short - "awakening_exp" / Int32ub, #int - "property1_property_id" / Int32ub, - "property1_value1" / Int32ub, - "property1_value2" / Int32ub, - "property2_property_id" / Int32ub, - "property2_value1" / Int32ub, - "property2_value2" / Int32ub, - "property3_property_id" / Int32ub, - "property3_value1" / Int32ub, - "property3_value2" / Int32ub, - "property4_property_id" / Int32ub, - "property4_value1" / Int32ub, - "property4_value2" / Int32ub, - "converted_card_num" / Int16ub, - "shop_purchase_flag" / Int8ul, # result is either 0 or 1 - "protect_flag" / Int8ul, # result is either 0 or 1 - "get_date_size" / Int32ub, # big endian - "get_date" / Int16ul[len(self.get_date)], - ) +class SaoGetAdventureExecUserDataResponse(SaoBaseResponse): + def __init__(self) -> None: + super().__init__(GameconnectCmd.GET_ADVENTURE_EXEC_USER_DATA_RESPONSE) + self.result = 1 + self.adventure_exec_user_data: List[AdventureExecUserData] = [] - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "after_equipment_user_data_size" / Rebuild(Int32ub, len_(this.after_equipment_user_data)), # big endian - "after_equipment_user_data" / Array(this.after_equipment_user_data_size, after_equipment_user_data_struct), - ) + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.adventure_exec_user_data) - resp_data = resp_struct.parse(resp_struct.build(dict( - result=self.result, - after_equipment_user_data_size=0, - after_equipment_user_data=[], - ))) +class SaoGetChatSideStoryUserDataListResponse(SaoBaseResponse): + def __init__(self) -> None: + super().__init__(GameconnectCmd.GET_CHAT_SIDE_STORY_USER_DATA_LIST_RESPONSE) + self.result = 1 + self.chat_side_story_user_data_list: List[ChatSideStoryUserData] = [] - synthesize_equipment_data = dict( - user_equipment_id_size=len(self.user_equipment_id) * 2, - user_equipment_id=[ord(x) for x in self.user_equipment_id], - equipment_id=self.equipment_id, - enhancement_value=self.enhancement_value, - max_enhancement_value_extended_num=self.max_enhancement_value_extended_num, - enhancement_exp=self.enhancement_exp, - possible_awakening_flag=self.possible_awakening_flag, - awakening_stage=self.awakening_stage, - awakening_exp=self.awakening_exp, - property1_property_id=self.property1_property_id, - property1_value1=self.property1_value1, - property1_value2=self.property1_value2, - property2_property_id=self.property2_property_id, - property2_value1=self.property2_value1, - property2_value2=self.property2_value2, - property3_property_id=self.property3_property_id, - property3_value1=self.property3_value1, - property3_value2=self.property3_value2, - property4_property_id=self.property4_property_id, - property4_value1=self.property4_value1, - property4_value2=self.property4_value2, - converted_card_num=self.converted_card_num, - shop_purchase_flag=self.shop_purchase_flag, - protect_flag=self.protect_flag, - get_date_size=len(self.get_date) * 2, - get_date=[ord(x) for x in self.get_date], + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.chat_side_story_user_data_list) - ) - - resp_data.after_equipment_user_data.append(synthesize_equipment_data) +class SaoBeginnerMissionAdConfirmNotificationRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.ticket_id, new_off = decode_str(data, off) + off += new_off - resp_data["after_equipment_user_data_size"] = len(resp_data.after_equipment_user_data) + self.user_id, new_off = decode_str(data, off) + off += new_off - # finally, rebuild the resp_data - resp_data = resp_struct.build(resp_data) - - self.length = len(resp_data) - return super().make() + resp_data + self.beginner_mission_id = decode_int(data, off) + off += INT_OFF class SaoGetDefragMatchBasicDataRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: super().__init__(header, data) + off = 0 + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.defrag_match_id = decode_int(data, off) + off += INT_OFF class SaoGetDefragMatchBasicDataResponse(SaoBaseResponse): - def __init__(self, cmd) -> None: - super().__init__(cmd) + def __init__(self) -> None: + super().__init__(GameconnectCmd.GET_DEFRAG_MATCH_BASIC_DATA_RESPONSE) self.result = 1 - self.defrag_match_basic_user_data_size = 1 # number of arrays - - self.seed_flag = 1 - self.ad_confirm_flag = 1 - self.total_league_point = 0 - self.have_league_score = 0 - self.class_num = 1 # 1 to 6 - self.hall_of_fame_confirm_flag = 0 + self.defrag_match_basic_user_data: List[DefragMatchBasicUserData] = [] def make(self) -> bytes: - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "defrag_match_basic_user_data_size" / Int32ub, # big endian - - "seed_flag" / Int16ub, #short - "ad_confirm_flag" / Int8ul, # result is either 0 or 1 - "total_league_point" / Int32ub, #int - "have_league_score" / Int16ub, #short - "class_num" / Int16ub, #short - "hall_of_fame_confirm_flag" / Int8ul, # result is either 0 or 1 - - ) - - resp_data = resp_struct.build(dict( - result=self.result, - defrag_match_basic_user_data_size=self.defrag_match_basic_user_data_size, - - seed_flag=self.seed_flag, - ad_confirm_flag=self.ad_confirm_flag, - total_league_point=self.total_league_point, - have_league_score=self.have_league_score, - class_num=self.class_num, - hall_of_fame_confirm_flag=self.hall_of_fame_confirm_flag, - )) - - self.length = len(resp_data) - return super().make() + resp_data + ret = encode_byte(self.result) + ret += encode_arr_cls(self.defrag_match_basic_user_data) + return super().make() + ret class SaoGetDefragMatchRankingUserDataRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: super().__init__(header, data) + off = 0 + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.defrag_match_id = decode_int(data, off) + off += INT_OFF class SaoGetDefragMatchRankingUserDataResponse(SaoBaseResponse): - def __init__(self, cmd) -> None: - super().__init__(cmd) + def __init__(self, profile: Dict) -> None: + super().__init__(GameconnectCmd.GET_DEFRAG_MATCH_RANKING_USER_DATA_RESPONSE) self.result = 1 - self.ranking_user_data_size = 1 # number of arrays + self.ranking_user_data: List[DefragMatchRankingUserData] = [DefragMatchRankingUserData.from_args(profile)] - self.league_point_rank = 1 - self.league_score_rank = 1 - self.nick_name = "PLAYER" - self.setting_title_id = 20005 # Default saved during profile creation, no changing for those atm - self.favorite_hero_log_id = 101000010 # Default saved during profile creation - self.favorite_hero_log_awakening_stage = 0 - self.favorite_support_log_id = 0 - self.favorite_support_log_awakening_stage = 0 - self.total_league_point = 1 - self.have_league_score = 1 - def make(self) -> bytes: - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "ranking_user_data_size" / Int32ub, # big endian - - "league_point_rank" / Int32ub, #int - "league_score_rank" / Int32ub, #int - "nick_name_size" / Int32ub, # big endian - "nick_name" / Int16ul[len(self.nick_name)], - "setting_title_id" / Int32ub, #int - "favorite_hero_log_id" / Int32ub, #int - "favorite_hero_log_awakening_stage" / Int16ub, #short - "favorite_support_log_id" / Int32ub, #int - "favorite_support_log_awakening_stage" / Int16ub, #short - "total_league_point" / Int32ub, #int - "have_league_score" / Int16ub, #short - ) - - resp_data = resp_struct.build(dict( - result=self.result, - ranking_user_data_size=self.ranking_user_data_size, - - league_point_rank=self.league_point_rank, - league_score_rank=self.league_score_rank, - nick_name_size=len(self.nick_name) * 2, - nick_name=[ord(x) for x in self.nick_name], - setting_title_id=self.setting_title_id, - favorite_hero_log_id=self.favorite_hero_log_id, - favorite_hero_log_awakening_stage=self.favorite_hero_log_awakening_stage, - favorite_support_log_id=self.favorite_support_log_id, - favorite_support_log_awakening_stage=self.favorite_support_log_awakening_stage, - total_league_point=self.total_league_point, - have_league_score=self.have_league_score, - )) - - self.length = len(resp_data) - return super().make() + resp_data + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.ranking_user_data) class SaoGetDefragMatchLeaguePointRankingListRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: super().__init__(header, data) + off = 0 + self.defrag_match_id = decode_int(data, off) + off += INT_OFF + + self.get_rank_start_num = decode_short(data, off) + off += SHORT_OFF + + self.get_rank_end_num = decode_short(data, off) + off += SHORT_OFF class SaoGetDefragMatchLeaguePointRankingListResponse(SaoBaseResponse): - def __init__(self, cmd) -> None: - super().__init__(cmd) + def __init__(self) -> None: + super().__init__(GameconnectCmd.GET_DEFRAG_MATCH_LEAGUE_POINT_RANKING_LIST_RESPONSE) self.result = 1 - self.ranking_user_data_size = 1 # number of arrays + self.ranking_data_list: List[DefragMatchLeaguePointRankingData] = [] - self.rank = 1 - self.user_id = "1" - self.store_id = "123" - self.store_name = "ARTEMiS" - self.nick_name = "PLAYER" - self.setting_title_id = 20005 - self.favorite_hero_log_id = 101000010 - self.favorite_hero_log_awakening_stage = 0 - self.favorite_support_log_id = 0 - self.favorite_support_log_awakening_stage = 0 - self.class_num = 1 - self.total_league_point = 1 - def make(self) -> bytes: - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "ranking_user_data_size" / Int32ub, # big endian - - "rank" / Int32ub, #int - "user_id_size" / Int32ub, # big endian - "user_id" / Int16ul[len(self.user_id)], - "store_id_size" / Int32ub, # big endian - "store_id" / Int16ul[len(self.store_id)], - "store_name_size" / Int32ub, # big endian - "store_name" / Int16ul[len(self.store_name)], - "nick_name_size" / Int32ub, # big endian - "nick_name" / Int16ul[len(self.nick_name)], - "setting_title_id" / Int32ub, #int - "favorite_hero_log_id" / Int32ub, #int - "favorite_hero_log_awakening_stage" / Int16ub, #short - "favorite_support_log_id" / Int32ub, #int - "favorite_support_log_awakening_stage" / Int16ub, #short - "class_num" / Int16ub, #short - "total_league_point" / Int32ub, #int - ) - - resp_data = resp_struct.build(dict( - result=self.result, - ranking_user_data_size=self.ranking_user_data_size, - - rank=self.rank, - user_id_size=len(self.user_id) * 2, - user_id=[ord(x) for x in self.user_id], - store_id_size=len(self.store_id) * 2, - store_id=[ord(x) for x in self.store_id], - store_name_size=len(self.store_name) * 2, - store_name=[ord(x) for x in self.store_name], - nick_name_size=len(self.nick_name) * 2, - nick_name=[ord(x) for x in self.nick_name], - setting_title_id=self.setting_title_id, - favorite_hero_log_id=self.favorite_hero_log_id, - favorite_hero_log_awakening_stage=self.favorite_hero_log_awakening_stage, - favorite_support_log_id=self.favorite_support_log_id, - favorite_support_log_awakening_stage=self.favorite_support_log_awakening_stage, - class_num=self.class_num, - total_league_point=self.total_league_point, - )) - - self.length = len(resp_data) - return super().make() + resp_data + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.ranking_data_list) class SaoGetDefragMatchLeagueScoreRankingListRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: super().__init__(header, data) + off = 0 + self.defrag_match_id = decode_int(data, off) + off += INT_OFF + + self.get_rank_start_num = decode_short(data, off) + off += SHORT_OFF + + self.get_rank_end_num = decode_short(data, off) + off += SHORT_OFF class SaoGetDefragMatchLeagueScoreRankingListResponse(SaoBaseResponse): - def __init__(self, cmd) -> None: - super().__init__(cmd) + def __init__(self) -> None: + super().__init__(GameconnectCmd.GET_DEFRAG_MATCH_LEAGUE_SCORE_RANKING_LIST_RESPONSE) self.result = 1 - self.ranking_user_data_size = 1 # number of arrays - - self.rank = 1 - self.user_id = "1" - self.store_id = "123" - self.store_name = "ARTEMiS" - self.nick_name = "PLAYER" - self.setting_title_id = 20005 - self.favorite_hero_log_id = 101000010 - self.favorite_hero_log_awakening_stage = 0 - self.favorite_support_log_id = 0 - self.favorite_support_log_awakening_stage = 0 - self.class_num = 1 - self.have_league_score = 1 + self.ranking_user_data: List[DefragMatchLeagueScoreRankingList] = [] def make(self) -> bytes: # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "ranking_user_data_size" / Int32ub, # big endian - - "rank" / Int32ub, #int - "user_id_size" / Int32ub, # big endian - "user_id" / Int16ul[len(self.user_id)], - "store_id_size" / Int32ub, # big endian - "store_id" / Int16ul[len(self.store_id)], - "store_name_size" / Int32ub, # big endian - "store_name" / Int16ul[len(self.store_name)], - "nick_name_size" / Int32ub, # big endian - "nick_name" / Int16ul[len(self.nick_name)], - "setting_title_id" / Int32ub, #int - "favorite_hero_log_id" / Int32ub, #int - "favorite_hero_log_awakening_stage" / Int16ub, #short - "favorite_support_log_id" / Int32ub, #int - "favorite_support_log_awakening_stage" / Int16ub, #short - "class_num" / Int16ub, #short - "have_league_score" / Int16ub, #short - ) - - resp_data = resp_struct.build(dict( - result=self.result, - ranking_user_data_size=self.ranking_user_data_size, - - rank=self.rank, - user_id_size=len(self.user_id) * 2, - user_id=[ord(x) for x in self.user_id], - store_id_size=len(self.store_id) * 2, - store_id=[ord(x) for x in self.store_id], - store_name_size=len(self.store_name) * 2, - store_name=[ord(x) for x in self.store_name], - nick_name_size=len(self.nick_name) * 2, - nick_name=[ord(x) for x in self.nick_name], - setting_title_id=self.setting_title_id, - favorite_hero_log_id=self.favorite_hero_log_id, - favorite_hero_log_awakening_stage=self.favorite_hero_log_awakening_stage, - favorite_support_log_id=self.favorite_support_log_id, - favorite_support_log_awakening_stage=self.favorite_support_log_awakening_stage, - class_num=self.class_num, - have_league_score=self.have_league_score, - )) - - self.length = len(resp_data) + resp_data = encode_byte(self.result) + resp_data += encode_arr_cls(self.ranking_user_data) return super().make() + resp_data +class SaoGetBeginnerMissionProgressesUserDataListRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.beginner_mission_id = decode_int(data, off) + off += INT_OFF + +class SaoGetBeginnerMissionProgressesUserDataListResponse(SaoBaseResponse): + def __init__(self) -> None: + super().__init__(GameconnectCmd.GET_BEGINNER_MISSION_PROGRESSES_USER_DATA_LIST_RESPONSE) + self.result = 1 + self.data_list: List[BeginnerMissionProgressesUserData] = [] + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetBeginnerMissionSeatProgressesUserDataListRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.beginner_mission_id = decode_int(data, off) + off += INT_OFF + +class SaoGetBeginnerMissionSeatProgressesUserDataListResponse(SaoBaseResponse): + def __init__(self) -> None: + super().__init__(GameconnectCmd.GET_BEGINNER_MISSION_SEAT_PROGRESSES_USER_DATA_LIST_RESPONSE) + self.result = 1 + self.data_list: List[BeginnerMissionSeatProgressesUserData] = [] + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetLinkedSiteRegCampaignUserDataRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.linked_site_reg_campaign_id = decode_int(data, off) + off += INT_OFF + +class SaoGetLinkedSiteRegCampaignUserDataResponse(SaoBaseResponse): + def __init__(self) -> None: + super().__init__(GameconnectCmd.GET_LINKED_SITE_REG_CAMPAIGN_USER_DATA_RESPONSE) + self.result = 1 + self.data: List[LinkedSiteRegCampaignUserData] = [] + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data) + +class SaoGetHeroLogUnitUserDataListResponse(SaoBaseResponse): + def __init__(self) -> None: + super().__init__(GameconnectCmd.GET_HERO_LOG_UNIT_USER_DATA_LIST_RESPONSE) + self.result = 1 + self.hero_log_unit_user_data_list: List[HeroLogUnitUserData] = [] + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.hero_log_unit_user_data_list) + +class SaoGetCharaUnitUserDataListResponse(SaoBaseResponse): + def __init__(self) -> None: + super().__init__(GameconnectCmd.GET_CHARA_UNIT_USER_DATA_LIST_RESPONSE) + self.result = 1 + self.chara_unit_user_data_list: List[CharaUnitUserData] = [] + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.chara_unit_user_data_list) + +class SaoBeginnerMissionAdConfirmNotificationRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.ticket_id, new_off = decode_str(data, off) + off += new_off + + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.beginner_mission_id = decode_int(data, off) + off += INT_OFF + class SaoBnidSerialCodeCheckRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: super().__init__(header, data) + off = 0 + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.bnid_serial_code, new_off = decode_str(data, off) + off += new_off class SaoBnidSerialCodeCheckResponse(SaoBaseResponse): - def __init__(self, cmd) -> None: - super().__init__(cmd) - + def __init__(self) -> None: + super().__init__(GameconnectCmd.BNID_SERIAL_CODE_CHECK_RESPONSE) self.result = 1 - self.bnid_item_id = "130050" + self.bnid_item_id = "" self.use_status = 0 - + def make(self) -> bytes: - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "bnid_item_id_size" / Int32ub, # big endian - "bnid_item_id" / Int16ul[len(self.bnid_item_id)], - "use_status" / Int8ul, # result is either 0 or 1 - ) + return super().make() \ + + encode_byte(self.result) \ + + encode_str(self.bnid_item_id) \ + + encode_byte(self.use_status) - resp_data = resp_struct.build(dict( - result=self.result, - bnid_item_id_size=len(self.bnid_item_id) * 2, - bnid_item_id=[ord(x) for x in self.bnid_item_id], - use_status=self.use_status, - )) +class SaoBnidSerialCodeEntryByAppendixCardRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.ticket_id, new_off = decode_str(data, off) + off += new_off - self.length = len(resp_data) - return super().make() + resp_data + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.bnid_serial_code, new_off = decode_str(data, off) + off += new_off + +class SaoBnidSerialCodeEntryByAppendixCardResponse(SaoBaseResponse): + def __init__(self) -> None: + super().__init__(GameconnectCmd.BNID_SERIAL_CODE_ENTRY_BY_APPENDIX_CARD_RESPONSE) + self.result = 1 + self.get_bnid_item_id = "" + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_str(self.get_bnid_item_id) + +class SaoDischargeProfileCardRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.ticket_id, new_off = decode_str(data, off) + off += new_off + + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.hero_log_user_hero_log_id, new_off = decode_str(data, off) + off += new_off + + self.main_weapon_user_equipment_id, new_off = decode_str(data, off) + off += new_off + + self.sub_equipment_user_equipment_id, new_off = decode_str(data, off) + off += new_off + + self.skill_id = decode_int(data, off) + off += INT_OFF + self.text_chara_message_id = decode_int(data, off) + off += INT_OFF + + self.holographic_flag = decode_byte(data, off) + off += BYTE_OFF + self.execute_print_type = PrintType(decode_byte(data, off)) + off += BYTE_OFF + +class SaoDischargeProfileCardResponse(SaoBaseResponse): + def __init__(self, serial: str) -> None: + super().__init__(GameconnectCmd.DISCHARGE_PROFILE_CARD_RESPONSE) + self.result = 1 + self.profile_card_code = serial + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_str(self.profile_card_code) + +class SaoDischargeResourceCardRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.ticket_id, new_off = decode_str(data, off) + off += new_off + + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.holographic_flag = decode_byte(data, off) + off += BYTE_OFF + + self.execute_print_type = PrintType(decode_byte(data, off)) + off += BYTE_OFF + + self.common_reward_user_data: List[MaterialCommonRewardUserData] = [] # typing lol + self.common_reward_user_data, new_off = decode_arr_cls(data, off, MaterialCommonRewardUserData) + off += new_off + +class SaoDischargeResourceCardResponse(SaoBaseResponse): + def __init__(self, serial: str) -> None: + super().__init__(GameconnectCmd.DISCHARGE_RESOURCE_CARD_RESPONSE) + self.result = 1 + self.profile_card_code = serial + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_str(self.profile_card_code) + +class SaoDischargeResourceCardCompleteRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.ticket_id, new_off = decode_str(data, off) + off += new_off + + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.resource_card_code, new_off = decode_str(data, off) + off += new_off class SaoScanQrQuestProfileCardRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: super().__init__(header, data) + off = 0 + self.ticket_id, new_off = decode_str(data, off) + off += new_off + + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.profile_card_code, new_off = decode_str(data, off) + off += new_off class SaoScanQrQuestProfileCardResponse(SaoBaseResponse): - def __init__(self, cmd) -> None: - super().__init__(cmd) + def __init__(self) -> None: + super().__init__(GameconnectCmd.SCAN_QR_QUEST_PROFILE_CARD_RESPONSE) self.result = 1 - - # read_profile_card_data - self.profile_card_code = "1234123412341234123" # ID of the QR code - self.nick_name = "PLAYER" - self.rank_num = 1 #short - self.setting_title_id = 20005 #int - self.skill_id = 0 #short - self.hero_log_hero_log_id = 118000230 #int - self.hero_log_log_level = 1 #short - self.hero_log_awakening_stage = 1 #short - - self.hero_log_property1_property_id = 0 #int - self.hero_log_property1_value1 = 0 #int - self.hero_log_property1_value2 = 0 #int - self.hero_log_property2_property_id = 0 #int - self.hero_log_property2_value1 = 0 #int - self.hero_log_property2_value2 = 0 #int - self.hero_log_property3_property_id = 0 #int - self.hero_log_property3_value1 = 0 #int - self.hero_log_property3_value2 = 0 #int - self.hero_log_property4_property_id = 0 #int - self.hero_log_property4_value1 = 0 #int - self.hero_log_property4_value2 = 0 #int - - self.main_weapon_equipment_id = 0 #int - self.main_weapon_enhancement_value = 0 #short - self.main_weapon_awakening_stage = 0 #short - - self.main_weapon_property1_property_id = 0 #int - self.main_weapon_property1_value1 = 0 #int - self.main_weapon_property1_value2 = 0 #int - self.main_weapon_property2_property_id = 0 #int - self.main_weapon_property2_value1 = 0 #int - self.main_weapon_property2_value2 = 0 #int - self.main_weapon_property3_property_id = 0 #int - self.main_weapon_property3_value1 = 0 #int - self.main_weapon_property3_value2 = 0 #int - self.main_weapon_property4_property_id = 0 #int - self.main_weapon_property4_value1 = 0 #int - self.main_weapon_property4_value2 = 0 #int - - self.sub_equipment_equipment_id = 0 #int - self.sub_equipment_enhancement_value = 0 #short - self.sub_equipment_awakening_stage = 0 #short - - self.sub_equipment_property1_property_id = 0 #int - self.sub_equipment_property1_value1 = 0 #int - self.sub_equipment_property1_value2 = 0 #int - self.sub_equipment_property2_property_id = 0 #int - self.sub_equipment_property2_value1 = 0 #int - self.sub_equipment_property2_value2 = 0 #int - self.sub_equipment_property3_property_id = 0 #int - self.sub_equipment_property3_value1 = 0 #int - self.sub_equipment_property3_value2 = 0 #int - self.sub_equipment_property4_property_id = 0 #int - self.sub_equipment_property4_value1 = 0 #int - self.sub_equipment_property4_value2 = 0 #int - - self.holographic_flag = 1 #byte + self.profile_card_data: List[ReadProfileCard] = [] def make(self) -> bytes: - #new stuff + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.profile_card_data) - read_profile_card_data_struct = Struct( - "profile_card_code_size" / Int32ub, # big endian - "profile_card_code" / Int16ul[len(self.profile_card_code)], - "nick_name_size" / Int32ub, # big endian - "nick_name" / Int16ul[len(self.nick_name)], - "rank_num" / Int16ub, #short - "setting_title_id" / Int32ub, #int - "skill_id" / Int16ub, #short - "hero_log_hero_log_id" / Int32ub, #int - "hero_log_log_level" / Int16ub, #short - "hero_log_awakening_stage" / Int16ub, #short - - "hero_log_property1_property_id" / Int32ub, #int - "hero_log_property1_value1" / Int32ub, #int - "hero_log_property1_value2" / Int32ub, #int - "hero_log_property2_property_id" / Int32ub, #int - "hero_log_property2_value1" / Int32ub, #int - "hero_log_property2_value2" / Int32ub, #int - "hero_log_property3_property_id" / Int32ub, #int - "hero_log_property3_value1" / Int32ub, #int - "hero_log_property3_value2" / Int32ub, #int - "hero_log_property4_property_id" / Int32ub, #int - "hero_log_property4_value1" / Int32ub, #int - "hero_log_property4_value2" / Int32ub, #int - - "main_weapon_equipment_id" / Int32ub, #int - "main_weapon_enhancement_value" / Int16ub, #short - "main_weapon_awakening_stage" / Int16ub, #short - - "main_weapon_property1_property_id" / Int32ub, #int - "main_weapon_property1_value1" / Int32ub, #int - "main_weapon_property1_value2" / Int32ub, #int - "main_weapon_property2_property_id" / Int32ub, #int - "main_weapon_property2_value1" / Int32ub, #int - "main_weapon_property2_value2" / Int32ub, #int - "main_weapon_property3_property_id" / Int32ub, #int - "main_weapon_property3_value1" / Int32ub, #int - "main_weapon_property3_value2" / Int32ub, #int - "main_weapon_property4_property_id" / Int32ub, #int - "main_weapon_property4_value1" / Int32ub, #int - "main_weapon_property4_value2" / Int32ub, #int - - "sub_equipment_equipment_id" / Int32ub, #int - "sub_equipment_enhancement_value" / Int16ub, #short - "sub_equipment_awakening_stage" / Int16ub, #short - - "sub_equipment_property1_property_id" / Int32ub, #int - "sub_equipment_property1_value1" / Int32ub, #int - "sub_equipment_property1_value2" / Int32ub, #int - "sub_equipment_property2_property_id" / Int32ub, #int - "sub_equipment_property2_value1" / Int32ub, #int - "sub_equipment_property2_value2" / Int32ub, #int - "sub_equipment_property3_property_id" / Int32ub, #int - "sub_equipment_property3_value1" / Int32ub, #int - "sub_equipment_property3_value2" / Int32ub, #int - "sub_equipment_property4_property_id" / Int32ub, #int - "sub_equipment_property4_value1" / Int32ub, #int - "sub_equipment_property4_value2" / Int32ub, #int - - "holographic_flag" / Int8ul, # result is either 0 or 1 - - ) - - # create a resp struct - resp_struct = Struct( - "result" / Int8ul, # result is either 0 or 1 - "read_profile_card_data_size" / Rebuild(Int32ub, len_(this.read_profile_card_data)), # big endian - "read_profile_card_data" / Array(this.read_profile_card_data_size, read_profile_card_data_struct), - ) - - resp_data = resp_struct.parse(resp_struct.build(dict( - result=self.result, - read_profile_card_data_size=0, - read_profile_card_data=[], - ))) - - hero_data = dict( - profile_card_code_size=len(self.profile_card_code) * 2, - profile_card_code=[ord(x) for x in self.profile_card_code], - nick_name_size=len(self.nick_name) * 2, - nick_name=[ord(x) for x in self.nick_name], - - rank_num=self.rank_num, - setting_title_id=self.setting_title_id, - skill_id=self.skill_id, - hero_log_hero_log_id=self.hero_log_hero_log_id, - hero_log_log_level=self.hero_log_log_level, - hero_log_awakening_stage=self.hero_log_awakening_stage, - - hero_log_property1_property_id=self.hero_log_property1_property_id, - hero_log_property1_value1=self.hero_log_property1_value1, - hero_log_property1_value2=self.hero_log_property1_value2, - hero_log_property2_property_id=self.hero_log_property2_property_id, - hero_log_property2_value1=self.hero_log_property2_value1, - hero_log_property2_value2=self.hero_log_property2_value2, - hero_log_property3_property_id=self.hero_log_property3_property_id, - hero_log_property3_value1=self.hero_log_property3_value1, - hero_log_property3_value2=self.hero_log_property3_value2, - hero_log_property4_property_id=self.hero_log_property4_property_id, - hero_log_property4_value1=self.hero_log_property4_value1, - hero_log_property4_value2=self.hero_log_property4_value2, - - main_weapon_equipment_id=self.main_weapon_equipment_id, - main_weapon_enhancement_value=self.main_weapon_enhancement_value, - main_weapon_awakening_stage=self.main_weapon_awakening_stage, - - main_weapon_property1_property_id=self.main_weapon_property1_property_id, - main_weapon_property1_value1=self.main_weapon_property1_value1, - main_weapon_property1_value2=self.main_weapon_property1_value2, - main_weapon_property2_property_id=self.main_weapon_property2_property_id, - main_weapon_property2_value1=self.main_weapon_property2_value1, - main_weapon_property2_value2=self.main_weapon_property2_value2, - main_weapon_property3_property_id=self.main_weapon_property3_property_id, - main_weapon_property3_value1=self.main_weapon_property3_value1, - main_weapon_property3_value2=self.main_weapon_property3_value2, - main_weapon_property4_property_id=self.main_weapon_property4_property_id, - main_weapon_property4_value1=self.main_weapon_property4_value1, - main_weapon_property4_value2=self.main_weapon_property4_value2, - - sub_equipment_equipment_id=self.sub_equipment_equipment_id, - sub_equipment_enhancement_value=self.sub_equipment_enhancement_value, - sub_equipment_awakening_stage=self.sub_equipment_awakening_stage, - - sub_equipment_property1_property_id=self.sub_equipment_property1_property_id, - sub_equipment_property1_value1=self.sub_equipment_property1_value1, - sub_equipment_property1_value2=self.sub_equipment_property1_value2, - sub_equipment_property2_property_id=self.sub_equipment_property2_property_id, - sub_equipment_property2_value1=self.sub_equipment_property2_value1, - sub_equipment_property2_value2=self.sub_equipment_property2_value2, - sub_equipment_property3_property_id=self.sub_equipment_property3_property_id, - sub_equipment_property3_value1=self.sub_equipment_property3_value1, - sub_equipment_property3_value2=self.sub_equipment_property3_value2, - sub_equipment_property4_property_id=self.sub_equipment_property4_property_id, - sub_equipment_property4_value1=self.sub_equipment_property4_value1, - sub_equipment_property4_value2=self.sub_equipment_property4_value2, - - holographic_flag=self.holographic_flag, - ) +class SaoScanQrShopResourceCardRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.ticket_id, new_off = decode_str(data, off) + off += new_off - resp_data.read_profile_card_data.append(hero_data) + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.resource_card_code, new_off = decode_str(data, off) + off += new_off - resp_data["read_profile_card_data_size"] = len(resp_data.read_profile_card_data) +class SaoScanQrQuestResourceCardRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.ticket_id, new_off = decode_str(data, off) + off += new_off + + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.resource_card_code, new_off = decode_str(data, off) + off += new_off - # finally, rebuild the resp_data - resp_data = resp_struct.build(resp_data) - - self.length = len(resp_data) - return super().make() + resp_data +class SaoScanQrQuestResourceCardResponse(SaoBaseResponse): + def __init__(self, reward_type: int = 0, reward_id: int = 0, is_holo: bool = False) -> None: + super().__init__(GameconnectCmd.SCAN_QR_QUEST_RESOURCE_CARD_RESPONSE) + self.result = 1 + self.common_reward_type = reward_type + self.common_reward_id = reward_id + self.holographic_flag = is_holo + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_short(self.common_reward_type) \ + + encode_int(self.common_reward_id) \ + + encode_bool(self.holographic_flag) + class SaoConsumeCreditGuestRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: super().__init__(header, data) @@ -3047,7 +1694,7 @@ class SaoConsumeCreditGuestRequest(SaoBaseRequest): self.cab_type = decode_byte(data, off) off += BYTE_OFF - self.act_type = decode_byte(data, off) + self.act_type = ActTypeConsumeCredit(decode_byte(data, off)) off += BYTE_OFF self.consume_num = decode_byte(data, off) @@ -3065,20 +1712,14 @@ class SaoChangePartyRequest(SaoBaseRequest): self.user_id = user_id[0] off += user_id[1] - self.act_type = decode_byte(data, off) + self.act_type = ActTypeChangeParty(decode_byte(data, off)) off += BYTE_OFF - self.party_data_count = decode_int(data, off) - off += INT_OFF - self.party_data_list: List[PartyData] = [] + self.party_data_list, new_off = decode_arr_cls(data, off, PartyData) + off += new_off - for _ in range(self.party_data_count): - tmp = PartyData(data, off) - self.party_data_list.append(tmp) - off += tmp.get_size() - -class TrialTowerPlayEndUnanalyzedLogFixed(SaoBaseRequest): +class SaoTrialTowerPlayEndUnanalyzedLogFixedRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: super().__init__(header, data) off = 0 @@ -3096,17 +1737,20 @@ class TrialTowerPlayEndUnanalyzedLogFixed(SaoBaseRequest): self.rarity_up_exec_flag = decode_byte(data, off) off += BYTE_OFF -class GetShopResourceSalesDataListRequest(SaoBaseRequest): - def __init__(self, header: SaoRequestHeader, data: bytes) -> None: - super().__init__(header, data) - off = 0 - user_id = decode_str(data, off) - self.user_id = user_id[0] - off += user_id[1] +class SaoTrialTowerPlayEndUnanalyzedLogFixedResponse(SaoBaseResponse): + def __init__(self) -> None: + super().__init__(GameconnectCmd.TRIAL_TOWER_PLAY_END_UNANALYZED_LOG_FIXED_RESPONSE) + self.result = 1 + self.play_end_unanalyzed_log_reward_data_list: List[QuestScenePlayEndUnanalyzedLogRewardData] = [] -class GetShopResourceSalesDataListResponse(SaoBaseResponse): - def __init__(self, cmd_id: int) -> None: - super().__init__(cmd_id) + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.play_end_unanalyzed_log_reward_data_list) + +class SaoGetShopResourceSalesDataListResponse(SaoBaseResponse): + def __init__(self) -> None: + super().__init__(GameconnectCmd.GET_SHOP_RESOURCE_SALES_DATA_LIST_RESPONSE) self.result = 1 # byte self.shop_resource_sales_data: List[ShopResourceSalesData] = [] @@ -3117,6 +1761,19 @@ class GetShopResourceSalesDataListResponse(SaoBaseResponse): self.header.length = len(ret) return super().make() + ret +class SaoPurchaseShopResourceRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.ticket_id, new_off = decode_str(data, off) + off += new_off + + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.user_shop_resource_id, new_off = decode_str(data, off) + off += new_off + class GetYuiMedalShopUserDataListRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: super().__init__(header, data) @@ -3159,78 +1816,6 @@ class GetGashaMedalShopUserDataListResponse(SaoBaseResponse): self.header.length = len(ret) return super().make() + ret -class GetMYuiMedalShopDataRequest(SaoBaseRequest): - def __init__(self, header: SaoRequestHeader, data: bytes) -> None: - super().__init__(header, data) - self.dummy = decode_byte(data, 0) - -class GetMYuiMedalShopDataResponse(SaoBaseResponse): - def __init__(self, cmd_id: int) -> None: - super().__init__(cmd_id) - self.result = 1 # byte - self.data_list: List[YuiMedalShopData] = [] - - def make(self) -> bytes: - ret = encode_byte(self.result) - ret += encode_arr_cls(self.data_list) - - self.header.length = len(ret) - return super().make() + ret - -class GetMYuiMedalShopItemsRequest(SaoBaseRequest): - def __init__(self, header: SaoRequestHeader, data: bytes) -> None: - super().__init__(header, data) - self.dummy = decode_byte(data, 0) - -class GetMYuiMedalShopItemsResponse(SaoBaseResponse): - def __init__(self, cmd_id: int) -> None: - super().__init__(cmd_id) - self.result = 1 # byte - self.data_list: List[YuiMedalShopItemData] = [] - - def make(self) -> bytes: - ret = encode_byte(self.result) - ret += encode_arr_cls(self.data_list) - - self.header.length = len(ret) - return super().make() + ret - -class GetMGashaMedalShopsRequest(SaoBaseRequest): - def __init__(self, header: SaoRequestHeader, data: bytes) -> None: - super().__init__(header, data) - self.dummy = decode_byte(data, 0) - -class GetMGashaMedalShopsResponse(SaoBaseResponse): - def __init__(self, cmd_id: int) -> None: - super().__init__(cmd_id) - self.result = 1 # byte - self.data_list: List[GashaMedalShop] = [] - - def make(self) -> bytes: - ret = encode_byte(self.result) - ret += encode_arr_cls(self.data_list) - - self.header.length = len(ret) - return super().make() + ret - -class GetMResEarnCampaignShopsRequest(SaoBaseRequest): - def __init__(self, header: SaoRequestHeader, data: bytes) -> None: - super().__init__(header, data) - self.dummy = decode_byte(data, 0) - -class GetMResEarnCampaignShopsResponse(SaoBaseResponse): - def __init__(self, cmd_id: int) -> None: - super().__init__(cmd_id) - self.result = 1 # byte - self.data_list: List[ResEarnCampaignShop] = [] - - def make(self) -> bytes: - ret = encode_byte(self.result) - ret += encode_arr_cls(self.data_list) - - self.header.length = len(ret) - return super().make() + ret - class SaoGiveFreeTicketRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: super().__init__(header, data) @@ -3244,15 +1829,6 @@ class SaoGiveFreeTicketRequest(SaoBaseRequest): self.give_num = decode_byte(data, off) off += BYTE_OFF -class SaoGiveFreeTicketResponse(SaoBaseResponse): - def __init__(self, cmd_id: int) -> None: - super().__init__(cmd_id) - self.result = 1 # byte - - def make(self) -> bytes: - ret = encode_byte(self.result) - return super().make() + ret - class SaoLogoutTicketUnpurchasedRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: super().__init__(header, data) @@ -3266,15 +1842,6 @@ class SaoLogoutTicketUnpurchasedRequest(SaoBaseRequest): self.cabinet_type = decode_byte(data, off) off += BYTE_OFF -class SaoLogoutTicketUnpurchasedResponse(SaoBaseResponse): - def __init__(self, cmd_id: int) -> None: - super().__init__(cmd_id) - self.result = 1 # byte - - def make(self) -> bytes: - ret = encode_byte(self.result) - return super().make() + ret - class SaoGetQuestHierarchyProgressDegreesRankingListRequest(SaoBaseRequest): def __init__(self, header: SaoRequestHeader, data: bytes) -> None: super().__init__(header, data) @@ -3322,3 +1889,2337 @@ class SaoGetQuestPopularHeroLogRankingListResponse(SaoBaseResponse): ret = encode_byte(self.result) ret += encode_arr_cls(self.quest_popular_hero_log_ranking_data_list) return super().make() + ret + +class SaoGetVariousTutorialDataListResponse(SaoBaseResponse): + def __init__(self) -> None: + super().__init__(GameconnectCmd.GET_VARIOUS_TUTORIAL_DATA_LIST_RESPONSE) + self.result = 1 + self.end_tutorial_type_list: List[int] = [] + + def make(self) -> bytes: + ret = encode_byte(self.result) + ret += encode_arr_num(self.end_tutorial_type_list, BYTE_OFF) + return super().make() + ret + +class SaoVariousTutorialEndRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.ticket_id, new_off = decode_str(data, off) + off += new_off + self.user_id, new_off = decode_str(data, off) + off += new_off + self.tutorial_type = decode_byte(data, off) + off += BYTE_OFF + +class SaoGetMExTowersRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.ex_tower_id_list, new_off = decode_arr_num(data, off, INT_OFF) + off += new_off + +class SaoGetMExTowerQuestsRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.ex_tower_id_list, new_off = decode_arr_num(data, off, INT_OFF) + off += new_off + +class SaoGetMChatEventStoriesRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.event_id_list, new_off = decode_arr_num(data, off, INT_OFF) + off += new_off + +class SaoGetMTreasureHuntsRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.treasure_hunt_id_list, new_off = decode_arr_num(data, off, INT_OFF) + off += new_off + +class SaoGetMTreasureHuntWholeTasksRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.treasure_hunt_id_list, new_off = decode_arr_num(data, off, INT_OFF) + off += new_off + +class SaoGetMTreasureHuntIndividualTasksRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.treasure_hunt_id_list, new_off = decode_arr_num(data, off, INT_OFF) + off += new_off + +class SaoGetMTreasureHuntSpecialEffectsRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.treasure_hunt_id_list, new_off = decode_arr_num(data, off, INT_OFF) + off += new_off + +class SaoGetMTreasureHuntEventPointRewardCommonRewardsRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.treasure_hunt_id_list, new_off = decode_arr_num(data, off, INT_OFF) + off += new_off + +class SaoGetMTreasureHuntEventPointRewardTitlesRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.treasure_hunt_id_list, new_off = decode_arr_num(data, off, INT_OFF) + off += new_off + +class SaoGetMDefragMatchesRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.defrag_match_id_list, new_off = decode_arr_num(data, off, INT_OFF) + off += new_off + +class SaoGetMDefragMatchSeedRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.defrag_match_id_list, new_off = decode_arr_num(data, off, INT_OFF) + off += new_off + +class SaoGetMDefragMatchSpecialEffectsRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.defrag_match_id_list, new_off = decode_arr_num(data, off, INT_OFF) + off += new_off + +class SaoGetMDefragMatchGradesRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.defrag_match_id_list, new_off = decode_arr_num(data, off, INT_OFF) + off += new_off + +class SaoGetMDefragMatchPeriodBonusesRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.defrag_match_id_list, new_off = decode_arr_num(data, off, INT_OFF) + off += new_off + +class SaoGetMDefragMatchRandomBonusTablesRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.defrag_match_id_list, new_off = decode_arr_num(data, off, INT_OFF) + off += new_off + +class SaoGetMDefragMatchRareDropsRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.defrag_match_id_list, new_off = decode_arr_num(data, off, INT_OFF) + off += new_off + +class SaoGetMEventScenesRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.event_id_list, new_off = decode_arr_num(data, off, INT_OFF) + off += new_off + +class SaoGetMBeginnerMissionConditionsRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.beginner_mission_id_list, new_off = decode_arr_num(data, off, INT_OFF) + off += new_off + +class SaoGetMBeginnerMissionRewardsRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.beginner_mission_id_list, new_off = decode_arr_num(data, off, INT_OFF) + off += new_off + +class SaoGetMBeginnerMissionSeatConditionsRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.beginner_mission_id_list, new_off = decode_arr_num(data, off, INT_OFF) + off += new_off + +class SaoGetMBeginnerMissionSeatRewardsRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.beginner_mission_id_list, new_off = decode_arr_num(data, off, INT_OFF) + off += new_off + +class SaoGetMEventMonstersRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.event_id_list, new_off = decode_arr_num(data, off, INT_OFF) + off += new_off + +class SaoGetMGashaMedalSettingsRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.gasha_id_list, new_off = decode_arr_num(data, off, INT_OFF) + off += new_off + +class SaoGetMGashaMedalShopItemsRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.gasha_medal_shop_id_list, new_off = decode_arr_num(data, off, INT_OFF) + off += new_off + +class SaoGetMResEarnCampaignApplicationProductsRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.res_earn_campaign_application_id_list, new_off = decode_arr_num(data, off, INT_OFF) + off += new_off + +class SaoGetMResEarnCampaignShopItemsRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.res_earn_campaign_shop_id_list, new_off = decode_arr_num(data, off, INT_OFF) + off += new_off + +class SaoGetMPlayCampaignRewardsRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.play_campaign_id_list, new_off = decode_arr_num(data, off, INT_OFF) + off += new_off + +class SaoGetMLinkedSiteRegCampaignRewardsRequest(SaoBaseRequest): + def __init__(self, header: SaoRequestHeader, data: bytes) -> None: + super().__init__(header, data) + off = 0 + self.linked_site_reg_campaign_id_list, new_off = decode_arr_num(data, off, INT_OFF) + off += new_off + +class SaoGetMPlayerRanksResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_PLAYER_RANKS_RESPONSE) + self.result = 1 + self.data_list: List[PlayerRankData] = [] + + if data_list: + for item in data_list: + self.data_list.append(PlayerRankData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMTitlesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_TITLES_RESPONSE) + self.result = 1 + self.data_list: List[TitleData] = [] + + if data_list: + for item in data_list: + self.data_list.append(TitleData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMFragmentsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_FRAGMENTS_RESPONSE) + self.result = 1 + self.data_list: List[FragmentData] = [] + + if data_list: + for item in data_list: + self.data_list.append(FragmentData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMRewardTablesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_REWARD_TABLES_RESPONSE) + self.result = 1 + self.data_list: List[RewardTableData] = [] + + if data_list: + for item in data_list: + self.data_list.append(RewardTableData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMRewardSetsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_REWARD_SETS_RESPONSE) + self.result = 1 + self.data_list: List[RewardSetData] = [] + + if data_list: + for item in data_list: + self.data_list.append(RewardSetData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMUnanalyzedLogGradesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_UNANALYZED_LOG_GRADES_RESPONSE) + self.result = 1 + self.data_list: List[UnanalyzedLogGradeData] = [] + + if data_list: + for item in data_list: + self.data_list.append(UnanalyzedLogGradeData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMAppointLeaderParamsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_APPOINT_LEADER_PARAMS_RESPONSE) + self.result = 1 + self.data_list: List[AppointLeaderParamData] = [] + + if data_list: + for item in data_list: + self.data_list.append(AppointLeaderParamData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMAppointLeaderEffectsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_APPOINT_LEADER_EFFECTS_RESPONSE) + self.result = 1 + self.data_list: List[AppointLeaderEffectData] = [] + + if data_list: + for item in data_list: + self.data_list.append(AppointLeaderEffectData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMAppointLeaderEffectTypesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_APPOINT_LEADER_EFFECT_TYPES_RESPONSE) + self.result = 1 + self.data_list: List[AppointLeaderEffectTypeData] = [] + + if data_list: + for item in data_list: + self.data_list.append(AppointLeaderEffectTypeData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMRaritiesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_RARITIES_RESPONSE) + self.result = 1 + self.data_list: List[RarityData] = [] + + if data_list: + for item in data_list: + self.data_list.append(RarityData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMCompositionEventsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_COMPOSITION_EVENTS_RESPONSE) + self.result = 1 + self.data_list: List[CompositionEventData] = [] + + if data_list: + for item in data_list: + self.data_list.append(CompositionEventData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMCompositionParamsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_COMPOSITION_PARAMS_RESPONSE) + self.result = 1 + self.data_list: List[CompositionParamData] = [] + + if data_list: + for item in data_list: + self.data_list.append(CompositionParamData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMGamePlayPricesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_GAME_PLAY_PRICES_RESPONSE) + self.result = 1 + self.data_list: List[GamePlayPriceData] = [] + + if data_list: + for item in data_list: + self.data_list.append(GamePlayPriceData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMBuyTicketsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_BUY_TICKETS_RESPONSE) + self.result = 1 + self.data_list: List[BuyTicketData] = [] + + if data_list: + for item in data_list: + self.data_list.append(BuyTicketData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMTipsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_TIPS_RESPONSE) + self.result = 1 + self.data_list: List[TipsData] = [] + + if data_list: + for item in data_list: + self.data_list.append(TipsData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMCapsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_CAPS_RESPONSE) + self.result = 1 + self.data_list: List[CapData] = [] + + if data_list: + for item in data_list: + self.data_list.append(CapData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMHeroLogResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_HERO_LOG_RESPONSE) + self.result = 1 + self.data_list: List[HeroLogData] = [] + + if data_list: + for item in data_list: + self.data_list.append(HeroLogData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMHeroLogLevelsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_HERO_LOG_LEVELS_RESPONSE) + self.result = 1 + self.data_list: List[HeroLogLevelData] = [] + + if data_list: + for item in data_list: + self.data_list.append(HeroLogLevelData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMHeroLogRolesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_HERO_LOG_ROLES_RESPONSE) + self.result = 1 + self.data_list: List[HeroLogRoleData] = [] + + if data_list: + for item in data_list: + self.data_list.append(HeroLogRoleData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMHeroLogTrustRanksResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_HERO_LOG_TRUST_RANKS_RESPONSE) + self.result = 1 + self.data_list: List[HeroLogTrustRankData] = [] + + if data_list: + for item in data_list: + self.data_list.append(HeroLogTrustRankData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMCharasResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_CHARAS_RESPONSE) + self.result = 1 + self.data_list: List[CharaData] = [] + + if data_list: + for item in data_list: + self.data_list.append(CharaData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMCharaFriendlyRanksResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_CHARA_FRIENDLY_RANKS_RESPONSE) + self.result = 1 + self.data_list: List[CharaFriendlyRankData] = [] + + if data_list: + for item in data_list: + self.data_list.append(CharaFriendlyRankData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMEquipmentsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_EQUIPMENTS_RESPONSE) + self.result = 1 + self.data_list: List[EquipmentData] = [] + + if data_list: + for item in data_list: + self.data_list.append(EquipmentData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMEquipmentLevelsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_EQUIPMENT_LEVELS_RESPONSE) + self.result = 1 + self.data_list: List[EquipmentLevelData] = [] + + if data_list: + for item in data_list: + self.data_list.append(EquipmentLevelData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMWeaponTypesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_WEAPON_TYPES_RESPONSE) + self.result = 1 + self.data_list: List[WeaponTypeData] = [] + + if data_list: + for item in data_list: + self.data_list.append(WeaponTypeData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMItemsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_ITEMS_RESPONSE) + self.result = 1 + self.data_list: List[ItemData] = [] + + if data_list: + for item in data_list: + self.data_list.append(ItemData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMItemTypesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_ITEM_TYPES_RESPONSE) + self.result = 1 + self.data_list: List[ItemTypeData] = [] + + if data_list: + for item in data_list: + self.data_list.append(ItemTypeData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMBuffItemsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_BUFF_ITEMS_RESPONSE) + self.result = 1 + self.data_list: List[BuffItemData] = [] + + if data_list: + for item in data_list: + self.data_list.append(BuffItemData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMEnemiesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_ENEMIES_RESPONSE) + self.result = 1 + self.data_list: List[EnemyData] = [] + + if data_list: + for item in data_list: + self.data_list.append(EnemyData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMEnemySetsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_ENEMY_SETS_RESPONSE) + self.result = 1 + self.data_list: List[EnemySetData] = [] + + if data_list: + for item in data_list: + self.data_list.append(EnemySetData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMEnemyKindsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_ENEMY_KINDS_RESPONSE) + self.result = 1 + self.data_list: List[EnemyKindData] = [] + + if data_list: + for item in data_list: + self.data_list.append(EnemyKindData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMEnemyCategoriesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_ENEMY_CATEGORIES_RESPONSE) + self.result = 1 + self.data_list: List[EnemyCategoryData] = [] + + if data_list: + for item in data_list: + self.data_list.append(EnemyCategoryData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMUnitsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_UNITS_RESPONSE) + self.result = 1 + self.data_list: List[UnitData] = [] + + if data_list: + for item in data_list: + self.data_list.append(UnitData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMUnitGimmicksResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_UNIT_GIMMICKS_RESPONSE) + self.result = 1 + self.data_list: List[UnitGimmickData] = [] + + if data_list: + for item in data_list: + self.data_list.append(UnitGimmickData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMUnitCollisionsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_UNIT_COLLISIONS_RESPONSE) + self.result = 1 + self.data_list: List[UnitCollisionData] = [] + + if data_list: + for item in data_list: + self.data_list.append(UnitCollisionData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMUnitPowersResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_UNIT_POWERS_RESPONSE) + self.result = 1 + self.data_list: List[UnitPowerData] = [] + + if data_list: + for item in data_list: + self.data_list.append(UnitPowerData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMGimmickAttacksResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_GIMMICK_ATTACKS_RESPONSE) + self.result = 1 + self.data_list: List[GimmickAttackData] = [] + + if data_list: + for item in data_list: + self.data_list.append(GimmickAttackData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMCharaAttacksResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_CHARA_ATTACKS_RESPONSE) + self.result = 1 + self.data_list: List[CharaAttackData] = [] + + if data_list: + for item in data_list: + self.data_list.append(CharaAttackData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMBossAttacksResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_BOSS_ATTACKS_RESPONSE) + self.result = 1 + self.data_list: List[BossAttackData] = [] + + if data_list: + for item in data_list: + self.data_list.append(BossAttackData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMMonsterAttacksResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_MONSTER_ATTACKS_RESPONSE) + self.result = 1 + self.data_list: List[MonsterAttackData] = [] + + if data_list: + for item in data_list: + self.data_list.append(MonsterAttackData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMMonsterActionsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_MONSTER_ACTIONS_RESPONSE) + self.result = 1 + self.data_list: List[MonsterActionData] = [] + + if data_list: + for item in data_list: + self.data_list.append(MonsterActionData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMPropertiesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_PROPERTIES_RESPONSE) + self.result = 1 + self.data_list: List[PropertyData] = [] + + if data_list: + for item in data_list: + self.data_list.append(PropertyData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMPropertyTablesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_PROPERTY_TABLES_RESPONSE) + self.result = 1 + self.data_list: List[PropertyTableData] = [] + + if data_list: + for item in data_list: + self.data_list.append(PropertyTableData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMPropertyTypesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_PROPERTY_TYPES_RESPONSE) + self.result = 1 + self.data_list: List[PropertyTypeData] = [] + + if data_list: + for item in data_list: + self.data_list.append(PropertyTypeData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMSkillsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_SKILLS_RESPONSE) + self.result = 1 + self.data_list: List[SkillData] = [] + + if data_list: + for item in data_list: + self.data_list.append(SkillData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMSkillTablesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_SKILL_TABLES_RESPONSE) + self.result = 1 + self.data_list: List[SkillTableData] = [] + + if data_list: + for item in data_list: + self.data_list.append(SkillTableData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMSkillLevelsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_SKILL_LEVELS_RESPONSE) + self.result = 1 + self.data_list: List[SkillLevelData] = [] + + if data_list: + for item in data_list: + self.data_list.append(SkillLevelData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMAwakeningsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_AWAKENINGS_RESPONSE) + self.result = 1 + self.data_list: List[AwakeningData] = [] + + if data_list: + for item in data_list: + self.data_list.append(AwakeningData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMSynchroSkillsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_SYNCHRO_SKILLS_RESPONSE) + self.result = 1 + self.data_list: List[SynchroSkillData] = [] + + if data_list: + for item in data_list: + self.data_list.append(SynchroSkillData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMSoundSkillCutInVoicesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_SOUND_SKILL_CUT_IN_VOICES_RESPONSE) + self.result = 1 + self.data_list: List[SoundSkillCutInVoiceData] = [] + + if data_list: + for item in data_list: + self.data_list.append(SoundSkillCutInVoiceData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMQuestScenesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_QUEST_SCENES_RESPONSE) + self.result = 1 + self.data_list: List[QuestSceneData] = [] + + if data_list: + for item in data_list: + self.data_list.append(QuestSceneData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMQuestExistUnitsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_QUEST_EXIST_UNITS_RESPONSE) + self.result = 1 + self.data_list: List[QuestExistUnitData] = [] + + if data_list: + for item in data_list: + self.data_list.append(QuestExistUnitData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMQuestEpisodeAppendRewardsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_QUEST_EPISODE_APPEND_REWARDS_RESPONSE) + self.result = 1 + self.data_list: List[QuestEpisodeAppendRewardData] = [] + + if data_list: + for item in data_list: + self.data_list.append(QuestEpisodeAppendRewardData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMSideQuestsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_SIDE_QUESTS_RESPONSE) + self.result = 1 + self.data_list: List[SideQuestData] = [] + + if data_list: + for item in data_list: + self.data_list.append(SideQuestData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMEpisodesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_EPISODES_RESPONSE) + self.result = 1 + self.data_list: List[EpisodeData] = [] + + if data_list: + for item in data_list: + self.data_list.append(EpisodeData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMEpisodeChaptersResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_EPISODE_CHAPTERS_RESPONSE) + self.result = 1 + self.data_list: List[EpisodeChapterData] = [] + + if data_list: + for item in data_list: + self.data_list.append(EpisodeChapterData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMEpisodePartsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_EPISODE_PARTS_RESPONSE) + self.result = 1 + self.data_list: List[EpisodePartData] = [] + + if data_list: + for item in data_list: + self.data_list.append(EpisodePartData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMTrialTowersResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_TRIAL_TOWERS_RESPONSE) + self.result = 1 + self.data_list: List[TrialTowerData] = [] + + if data_list: + for item in data_list: + self.data_list.append(TrialTowerData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMExTowersResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_EX_TOWERS_RESPONSE) + self.result = 1 + self.data_list: List[ExTowerData] = [] + + if data_list: + for item in data_list: + self.data_list.append(ExTowerData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMExTowerQuestsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_EX_TOWER_QUESTS_RESPONSE) + self.result = 1 + self.data_list: List[ExTowerQuestData] = [] + + if data_list: + for item in data_list: + self.data_list.append(ExTowerQuestData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMMenuDisplayEnemiesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_MENU_DISPLAY_ENEMIES_RESPONSE) + self.result = 1 + self.data_list: List[MenuDisplayEnemyData] = [] + + if data_list: + for item in data_list: + self.data_list.append(MenuDisplayEnemyData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMMissionsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_MISSIONS_RESPONSE) + self.result = 1 + self.data_list: List[MissionData] = [] + + if data_list: + for item in data_list: + self.data_list.append(MissionData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMMissionTablesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_MISSION_TABLES_RESPONSE) + self.result = 1 + self.data_list: List[MissionTableData] = [] + + if data_list: + for item in data_list: + self.data_list.append(MissionTableData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMMissionDifficultiesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_MISSION_DIFFICULTIES_RESPONSE) + self.result = 1 + self.data_list: List[MissionDifficultyData] = [] + + if data_list: + for item in data_list: + self.data_list.append(MissionDifficultyData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMBattleCamerasResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_BATTLE_CAMERAS_RESPONSE) + self.result = 1 + self.data_list: List[BattleCameraData] = [] + + if data_list: + for item in data_list: + self.data_list.append(BattleCameraData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMChatMainStoriesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_CHAT_MAIN_STORIES_RESPONSE) + self.result = 1 + self.data_list: List[ChatMainStoryData] = [] + + if data_list: + for item in data_list: + self.data_list.append(ChatMainStoryData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMChatSideStoriesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_CHAT_SIDE_STORIES_RESPONSE) + self.result = 1 + self.data_list: List[ChatSideStoryData] = [] + + if data_list: + for item in data_list: + self.data_list.append(ChatSideStoryData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMChatEventStoriesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_CHAT_EVENT_STORIES_RESPONSE) + self.result = 1 + self.data_list: List[ChatEventStoryData] = [] + + if data_list: + for item in data_list: + self.data_list.append(ChatEventStoryData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMNavigatorCharasResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_NAVIGATOR_CHARAS_RESPONSE) + self.result = 1 + self.data_list: List[NavigatorCharaData] = [] + + if data_list: + for item in data_list: + self.data_list.append(NavigatorCharaData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMNavigatorCommentsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_NAVIGATOR_COMMENTS_RESPONSE) + self.result = 1 + self.data_list: List[NavigatorCommentData] = [] + + if data_list: + for item in data_list: + self.data_list.append(NavigatorCommentData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMExBonusTablesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_EX_BONUS_TABLES_RESPONSE) + self.result = 1 + self.data_list: List[ExBonusTableData] = [] + + if data_list: + for item in data_list: + self.data_list.append(ExBonusTableData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMExBonusConditionsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_EX_BONUS_CONDITIONS_RESPONSE) + self.result = 1 + self.data_list: List[ExBonusConditionData] = [] + + if data_list: + for item in data_list: + self.data_list.append(ExBonusConditionData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMQuestRareDropsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_QUEST_RARE_DROPS_RESPONSE) + self.result = 1 + self.data_list: List[QuestRareDropData] = [] + + if data_list: + for item in data_list: + self.data_list.append(QuestRareDropData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMQuestSpecialRareDropSettingsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_QUEST_SPECIAL_RARE_DROP_SETTINGS_RESPONSE) + self.result = 1 + self.data_list: List[QuestSpecialRareDropSettingData] = [] + + if data_list: + for item in data_list: + self.data_list.append(QuestSpecialRareDropSettingData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMQuestSpecialRareDropsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_QUEST_SPECIAL_RARE_DROPS_RESPONSE) + self.result = 1 + self.data_list: List[QuestSpecialRareDropData] = [] + + if data_list: + for item in data_list: + self.data_list.append(QuestSpecialRareDropData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMQuestTutorialsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_QUEST_TUTORIALS_RESPONSE) + self.result = 1 + self.data_list: List[QuestTutorialData] = [] + + if data_list: + for item in data_list: + self.data_list.append(QuestTutorialData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMQuestPlayerTraceTablesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_QUEST_PLAYER_TRACE_TABLES_RESPONSE) + self.result = 1 + self.data_list: List[QuestPlayerTraceTableData] = [] + + if data_list: + for item in data_list: + self.data_list.append(QuestPlayerTraceTableData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMQuestStillsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_QUEST_STILLS_RESPONSE) + self.result = 1 + self.data_list: List[QuestStillData] = [] + + if data_list: + for item in data_list: + self.data_list.append(QuestStillData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMGashasResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_GASHAS_RESPONSE) + self.result = 1 + self.data_list: List[GashaData] = [] + + if data_list: + for item in data_list: + self.data_list.append(GashaData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMGashaHeadersResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_GASHA_HEADERS_RESPONSE) + self.result = 1 + self.data_list: List[GashaHeaderData] = [] + + if data_list: + for item in data_list: + self.data_list.append(GashaHeaderData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMGashaLotteryRaritiesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_GASHA_LOTTERY_RARITIES_RESPONSE) + self.result = 1 + self.data_list: List[GashaLotteryRarityData] = [] + + if data_list: + for item in data_list: + self.data_list.append(GashaLotteryRarityData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMGashaPrizesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_GASHA_PRIZES_RESPONSE) + self.result = 1 + self.data_list: List[GashaPrizeData] = [] + + if data_list: + for item in data_list: + self.data_list.append(GashaPrizeData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMComebackEventsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_COMEBACK_EVENTS_RESPONSE) + self.result = 1 + self.data_list: List[ComebackEventData] = [] + + if data_list: + for item in data_list: + self.data_list.append(ComebackEventData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMAdBannersResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_AD_BANNERS_RESPONSE) + self.result = 1 + self.data_list: List[AdBannerData] = [] + + if data_list: + for item in data_list: + self.data_list.append(AdBannerData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMEventsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_EVENTS_RESPONSE) + self.result = 1 + self.data_list: List[EventsData] = [] + + if data_list: + for item in data_list: + self.data_list.append(EventsData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMTreasureHuntsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_TREASURE_HUNTS_RESPONSE) + self.result = 1 + self.data_list: List[TreasureHuntsData] = [] + + if data_list: + for item in data_list: + self.data_list.append(TreasureHuntsData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMTreasureHuntWholeTasksResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_TREASURE_HUNT_WHOLE_TASKS_RESPONSE) + self.result = 1 + self.data_list: List[TreasureHuntWholeTasksData] = [] + + if data_list: + for item in data_list: + self.data_list.append(TreasureHuntWholeTasksData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMTreasureHuntIndividualTasksResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_TREASURE_HUNT_INDIVIDUAL_TASKS_RESPONSE) + self.result = 1 + self.data_list: List[TreasureHuntIndividualTasksData] = [] + + if data_list: + for item in data_list: + self.data_list.append(TreasureHuntIndividualTasksData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMTreasureHuntSpecialEffectsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_TREASURE_HUNT_SPECIAL_EFFECTS_RESPONSE) + self.result = 1 + self.data_list: List[TreasureHuntSpecialEffectsData] = [] + + if data_list: + for item in data_list: + self.data_list.append(TreasureHuntSpecialEffectsData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMTreasureHuntEventPointRewardCommonRewardsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_TREASURE_HUNT_EVENT_POINT_REWARD_COMMON_REWARDS_RESPONSE) + self.result = 1 + self.data_list: List[TreasureHuntEventPointRewardCommonRewardsData] = [] + + if data_list: + for item in data_list: + self.data_list.append(TreasureHuntEventPointRewardCommonRewardsData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMTreasureHuntEventPointRewardTitlesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_TREASURE_HUNT_EVENT_POINT_REWARD_TITLES_RESPONSE) + self.result = 1 + self.data_list: List[TreasureHuntEventPointRewardTitlesData] = [] + + if data_list: + for item in data_list: + self.data_list.append(TreasureHuntEventPointRewardTitlesData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMTreasureHuntTaskTextsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_TREASURE_HUNT_TASK_TEXTS_RESPONSE) + self.result = 1 + self.data_list: List[TreasureHuntTaskTextData] = [] + + if data_list: + for item in data_list: + self.data_list.append(TreasureHuntTaskTextData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMBnidSerialCodesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_BNID_SERIAL_CODES_RESPONSE) + self.result = 1 + self.data_list: List[BnidSerialCodeData] = [] + + if data_list: + for item in data_list: + self.data_list.append(BnidSerialCodeData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMBnidSerialCodeRewardsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_BNID_SERIAL_CODE_REWARDS_RESPONSE) + self.result = 1 + self.data_list: List[BnidSerialCodeRewardData] = [] + + if data_list: + for item in data_list: + self.data_list.append(BnidSerialCodeRewardData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMSupportLogResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_SUPPORT_LOG_RESPONSE) + self.result = 1 + self.data_list: List[SupportLogData] = [] + + if data_list: + for item in data_list: + self.data_list.append(SupportLogData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMSupportLogTypesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_SUPPORT_LOG_TYPES_RESPONSE) + self.result = 1 + self.data_list: List[SupportLogTypeData] = [] + + if data_list: + for item in data_list: + self.data_list.append(SupportLogTypeData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMEpisodeAppendsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_EPISODE_APPENDS_RESPONSE) + self.result = 1 + self.data_list: List[EpisodeAppendData] = [] + + if data_list: + for item in data_list: + self.data_list.append(EpisodeAppendData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMQuestDefragMatchQuestsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_QUEST_DEFRAG_MATCH_QUESTS_RESPONSE) + self.result = 1 + self.data_list: List[QuestDefragMatchQuestData] = [] + + if data_list: + for item in data_list: + self.data_list.append(QuestDefragMatchQuestData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMQuestDefragMatchQuestBossTablesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_QUEST_DEFRAG_MATCH_QUEST_BOSS_TABLES_RESPONSE) + self.result = 1 + self.data_list: List[QuestDefragMatchQuestBossTableData] = [] + + if data_list: + for item in data_list: + self.data_list.append(QuestDefragMatchQuestBossTableData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMDefragMatchesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_DEFRAG_MATCHES_RESPONSE) + self.result = 1 + self.data_list: List[DefragMatchData] = [] + + if data_list: + for item in data_list: + self.data_list.append(DefragMatchData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMDefragMatchSeedResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_DEFRAG_MATCH_SEED_RESPONSE) + self.result = 1 + self.data_list: List[DefragMatchSeedData] = [] + + if data_list: + for item in data_list: + self.data_list.append(DefragMatchSeedData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMDefragMatchSpecialEffectsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_DEFRAG_MATCH_SPECIAL_EFFECTS_RESPONSE) + self.result = 1 + self.data_list: List[DefragMatchSpecialEffectData] = [] + + if data_list: + for item in data_list: + self.data_list.append(DefragMatchSpecialEffectData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMDefragMatchGradesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_DEFRAG_MATCH_GRADES_RESPONSE) + self.result = 1 + self.data_list: List[DefragMatchGradeData] = [] + + if data_list: + for item in data_list: + self.data_list.append(DefragMatchGradeData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMDefragMatchCpuUnitsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_DEFRAG_MATCH_CPU_UNITS_RESPONSE) + self.result = 1 + self.data_list: List[DefragMatchCpuUnitData] = [] + + if data_list: + for item in data_list: + self.data_list.append(DefragMatchCpuUnitData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMDefragMatchCpuSupportLogsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_DEFRAG_MATCH_CPU_SUPPORT_LOGS_RESPONSE) + self.result = 1 + self.data_list: List[DefragMatchCpuSupportLogData] = [] + + if data_list: + for item in data_list: + self.data_list.append(DefragMatchCpuSupportLogData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMDefragMatchPeriodBonusesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_DEFRAG_MATCH_PERIOD_BONUSES_RESPONSE) + self.result = 1 + self.data_list: List[DefragMatchPeriodBonusData] = [] + + if data_list: + for item in data_list: + self.data_list.append(DefragMatchPeriodBonusData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMDefragMatchRandomBonusTablesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_DEFRAG_MATCH_RANDOM_BONUS_TABLES_RESPONSE) + self.result = 1 + self.data_list: List[DefragMatchRandomBonusTableData] = [] + + if data_list: + for item in data_list: + self.data_list.append(DefragMatchRandomBonusTableData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMDefragMatchRandomBonusConditionsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_DEFRAG_MATCH_RANDOM_BONUS_CONDITIONS_RESPONSE) + self.result = 1 + self.data_list: List[DefragMatchRandomBonusConditionData] = [] + + if data_list: + for item in data_list: + self.data_list.append(DefragMatchRandomBonusConditionData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMDefragMatchRareDropsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_DEFRAG_MATCH_RARE_DROPS_RESPONSE) + self.result = 1 + self.data_list: List[DefragMatchRareDropData] = [] + + if data_list: + for item in data_list: + self.data_list.append(DefragMatchRareDropData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMYuiMedalShopsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_YUI_MEDAL_SHOPS_RESPONSE) + self.result = 1 + self.data_list: List[YuiMedalShopData] = [] + + if data_list: + for item in data_list: + self.data_list.append(YuiMedalShopData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMYuiMedalShopItemsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_YUI_MEDAL_SHOP_ITEMS_RESPONSE) + self.result = 1 + self.data_list: List[YuiMedalShopItemData] = [] + + if data_list: + for item in data_list: + self.data_list.append(YuiMedalShopItemData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMEventScenesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_EVENT_SCENES_RESPONSE) + self.result = 1 + self.data_list: List[EventSceneData] = [] + + if data_list: + for item in data_list: + self.data_list.append(EventSceneData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMGenericCampaignPeriodsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_GENERIC_CAMPAIGN_PERIODS_RESPONSE) + self.result = 1 + self.data_list: List[GenericCampaignPeriodData] = [] + + if data_list: + for item in data_list: + self.data_list.append(GenericCampaignPeriodData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMBeginnerMissionsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_BEGINNER_MISSIONS_RESPONSE) + self.result = 1 + self.data_list: List[BeginnerMissionData] = [] + + if data_list: + for item in data_list: + self.data_list.append(BeginnerMissionData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMBeginnerMissionConditionsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_BEGINNER_MISSION_CONDITIONS_RESPONSE) + self.result = 1 + self.data_list: List[BeginnerMissionConditionData] = [] + + if data_list: + for item in data_list: + self.data_list.append(BeginnerMissionConditionData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMBeginnerMissionRewardsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_BEGINNER_MISSION_REWARDS_RESPONSE) + self.result = 1 + self.data_list: List[BeginnerMissionRewardData] = [] + + if data_list: + for item in data_list: + self.data_list.append(BeginnerMissionRewardData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMBeginnerMissionSeatConditionsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_BEGINNER_MISSION_SEAT_CONDITIONS_RESPONSE) + self.result = 1 + self.data_list: List[BeginnerMissionSeatConditionData] = [] + + if data_list: + for item in data_list: + self.data_list.append(BeginnerMissionSeatConditionData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMBeginnerMissionSeatRewardsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_BEGINNER_MISSION_SEAT_REWARDS_RESPONSE) + self.result = 1 + self.data_list: List[BeginnerMissionSeatRewardData] = [] + + if data_list: + for item in data_list: + self.data_list.append(BeginnerMissionSeatRewardData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMEventItemsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_EVENT_ITEMS_RESPONSE) + self.result = 1 + self.data_list: List[EventItemData] = [] + + if data_list: + for item in data_list: + self.data_list.append(EventItemData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMEventMonstersResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_EVENT_MONSTERS_RESPONSE) + self.result = 1 + self.data_list: List[EventMonsterData] = [] + + if data_list: + for item in data_list: + self.data_list.append(EventMonsterData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMYuiMedalBonusesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_YUI_MEDAL_BONUSES_RESPONSE) + self.result = 1 + self.data_list: List[YuiMedalBonusData] = [] + + if data_list: + for item in data_list: + self.data_list.append(YuiMedalBonusData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMYuiMedalBonusConditionsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_YUI_MEDAL_BONUS_CONDITIONS_RESPONSE) + self.result = 1 + self.data_list: List[YuiMedalBonusConditionData] = [] + + if data_list: + for item in data_list: + self.data_list.append(YuiMedalBonusConditionData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMGashaMedalsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_GASHA_MEDALS_RESPONSE) + self.result = 1 + self.data_list: List[GashaMedalData] = [] + + if data_list: + for item in data_list: + self.data_list.append(GashaMedalData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMGashaMedalTypesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_GASHA_MEDAL_TYPES_RESPONSE) + self.result = 1 + self.data_list: List[GashaMedalTypeData] = [] + + if data_list: + for item in data_list: + self.data_list.append(GashaMedalTypeData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMGashaMedalSettingsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_GASHA_MEDAL_SETTINGS_RESPONSE) + self.result = 1 + self.data_list: List[GashaMedalSettingData] = [] + + if data_list: + for item in data_list: + self.data_list.append(GashaMedalSettingData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMGashaMedalBonusesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_GASHA_MEDAL_BONUSES_RESPONSE) + self.result = 1 + self.data_list: List[GashaMedalBonusData] = [] + + if data_list: + for item in data_list: + self.data_list.append(GashaMedalBonusData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMGashaMedalShopsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_GASHA_MEDAL_SHOPS_RESPONSE) + self.result = 1 + self.data_list: List[GashaMedalShopData] = [] + + if data_list: + for item in data_list: + self.data_list.append(GashaMedalShopData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMGashaMedalShopItemsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_GASHA_MEDAL_SHOP_ITEMS_RESPONSE) + self.result = 1 + self.data_list: List[GashaMedalShopItemData] = [] + + if data_list: + for item in data_list: + self.data_list.append(GashaMedalShopItemData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMResEarnCampaignApplicationsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_RES_EARN_CAMPAIGN_APPLICATIONS_RESPONSE) + self.result = 1 + self.data_list: List[ResEarnCampaignApplicationData] = [] + + if data_list: + for item in data_list: + self.data_list.append(ResEarnCampaignApplicationData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMResEarnCampaignApplicationProductsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_RES_EARN_CAMPAIGN_APPLICATION_PRODUCTS_RESPONSE) + self.result = 1 + self.data_list: List[ResEarnCampaignApplicationProductData] = [] + + if data_list: + for item in data_list: + self.data_list.append(ResEarnCampaignApplicationProductData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMResEarnCampaignShopsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_RES_EARN_CAMPAIGN_SHOPS_RESPONSE) + self.result = 1 + self.data_list: List[ResEarnCampaignShopData] = [] + + if data_list: + for item in data_list: + self.data_list.append(ResEarnCampaignShopData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMResEarnCampaignShopItemsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_RES_EARN_CAMPAIGN_SHOP_ITEMS_RESPONSE) + self.result = 1 + self.data_list: List[ResEarnCampaignShopItemData] = [] + + if data_list: + for item in data_list: + self.data_list.append(ResEarnCampaignShopItemData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMPayingYuiMedalBonusesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_PAYING_YUI_MEDAL_BONUSES_RESPONSE) + self.result = 1 + self.data_list: List[PayingYuiMedalBonusData] = [] + + if data_list: + for item in data_list: + self.data_list.append(PayingYuiMedalBonusData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMAcLoginBonusesResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_AC_LOGIN_BONUSES_RESPONSE) + self.result = 1 + self.data_list: List[AcLoginBonusData] = [] + + if data_list: + for item in data_list: + self.data_list.append(AcLoginBonusData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMPlayCampaignsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_PLAY_CAMPAIGNS_RESPONSE) + self.result = 1 + self.data_list: List[PlayCampaignData] = [] + + if data_list: + for item in data_list: + self.data_list.append(PlayCampaignData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMPlayCampaignRewardsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_PLAY_CAMPAIGN_REWARDS_RESPONSE) + self.result = 1 + self.data_list: List[PlayCampaignRewardData] = [] + + if data_list: + for item in data_list: + self.data_list.append(PlayCampaignRewardData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMGashaFreeCampaignsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_GASHA_FREE_CAMPAIGNS_RESPONSE) + self.result = 1 + self.data_list: List[GashaFreeCampaignData] = [] + + if data_list: + for item in data_list: + self.data_list.append(GashaFreeCampaignData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMQuestDropBoostCampaignsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_QUEST_DROP_BOOST_CAMPAIGNS_RESPONSE) + self.result = 1 + self.data_list: List[QuestDropBoostCampaignData] = [] + + if data_list: + for item in data_list: + self.data_list.append(QuestDropBoostCampaignData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMFirstTicketPurchaseCampaignsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_FIRST_TICKET_PURCHASE_CAMPAIGNS_RESPONSE) + self.result = 1 + self.data_list: List[FirstTicketPurchaseCampaignData] = [] + + if data_list: + for item in data_list: + self.data_list.append(FirstTicketPurchaseCampaignData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMLinkedSiteRegCampaignsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_LINKED_SITE_REG_CAMPAIGNS_RESPONSE) + self.result = 1 + self.data_list: List[LinkedSiteRegCampaignsData] = [] + + if data_list: + for item in data_list: + self.data_list.append(LinkedSiteRegCampaignsData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + +class SaoGetMLinkedSiteRegCampaignRewardsResponse(SaoBaseResponse): + def __init__(self, data_list: List[Dict]) -> None: + super().__init__(GameconnectCmd.GET_M_LINKED_SITE_REG_CAMPAIGN_REWARDS_RESPONSE) + self.result = 1 + self.data_list: List[LinkedSiteRegCampaignRewardData] = [] + + if data_list: + for item in data_list: + self.data_list.append(LinkedSiteRegCampaignRewardData.from_args(item)) + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.result) \ + + encode_arr_cls(self.data_list) + diff --git a/titles/sao/handlers/helpers.py b/titles/sao/handlers/helpers.py index 97a15b2..6b91c7b 100644 --- a/titles/sao/handlers/helpers.py +++ b/titles/sao/handlers/helpers.py @@ -1,7 +1,8 @@ -from typing import Tuple, List, Optional +from typing import Tuple, List, Optional, Dict import struct import logging from datetime import datetime +from enum import IntEnum BIGINT_OFF = 16 LONG_OFF = 8 @@ -78,31 +79,64 @@ def decode_arr_str(data: bytes, offset: int) -> Tuple[List[str], int]: return (ret, size) +def decode_date_str(data: bytes, offset: int) -> Tuple[datetime, int]: + s, new_o = decode_str(data, offset) + return (prs_dt(s), new_o) + +def decode_bool(data: bytes, offset: int) -> bool: + return bool(decode_byte(data, offset)) + def encode_byte(data: int) -> bytes: - return struct.pack("!b", data) + if data is None: + return b"\0" + try: + return struct.pack("!b", int(data)) + except Exception as e: + logging.getLogger('sao').error(f"Failed to encode {data} as byte! - {e}") + return b"\0" def encode_short(data: int) -> bytes: - return struct.pack("!h", data) + if data is None: + return b"\0\0" + try: + return struct.pack("!h", int(data)) + except Exception as e: + logging.getLogger('sao').error(f"Failed to encode {data} as short! - {e}") + return b"\0\0" def encode_int(data: int) -> bytes: - return struct.pack("!i", data) + if data is None: + return b"\0\0\0\0" + try: + return struct.pack("!i", int(data)) + except Exception as e: + logging.getLogger('sao').error(f"Failed to encode {data} as int! - {e}") + return b"\0\0\0\0" def encode_long(data: int) -> bytes: - return struct.pack("!l", data) + if data is None: + return b"\0\0\0\0\0\0\0\0" + return struct.pack("!l", int(data)) def encode_bigint(data: int) -> bytes: - return struct.pack("!q", data) + if data is None: + return b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + return struct.pack("!q", int(data)) def encode_str(s: str) -> bytes: + if s is None: + return b"\0\0\0\0" try: - str_bytes = s.encode("utf-16-le", errors="replace") + str_bytes = str(s).encode("utf-16-le", errors="replace") str_len_bytes = struct.pack("!I", len(str_bytes)) return str_len_bytes + str_bytes - except: - logging.getLogger('sao').error(f"Failed to encode {s} as bytes!") + except Exception as e: + logging.getLogger('sao').error(f"Failed to encode {s} as bytes! - {e}") return b"" def encode_arr_num(data: List[int], element_size: int) -> bytes: + if data is None: + return b"\0\0\0\0" ret = encode_int(len(data)) if element_size == BYTE_OFF: @@ -126,6 +160,120 @@ def encode_arr_num(data: List[int], element_size: int) -> bytes: return ret +def encode_bool(b: bool) -> bytes: + if b is None: + return b"\0" + return encode_byte(int(b)) + +def encode_date_str(d: datetime) -> bytes: + return encode_str(fmt_dt(d)) + +class PrintType(IntEnum): + NONE = 0 + FromStorage = 1 + FromGasha = 2 + +class AuthType(IntEnum): + UNKNOWN = 0 + CARD = 1 + MOBLE = 2 + +class ActTypeConsumeTicket(IntEnum): + QuestEpisodeBeginner = 1 + QuestEpisode = 2 + QuestTrialTowerBeginner = 3 + QuestTrialTower = 4 + QuestPvEBeginner = 5 + QuestPvE = 6 + QuestContinue = 7 + QuesRarityUp = 8 + QuestEpisodeBoost = 9 + QuestTrialTowerBoost = 10 + QuestPvEBoost = 11 + CustomModeExtend = 21 + CustomModeRetry = 22 + YuiMedalShop = 31 + +class ActTypeChangeParty(IntEnum): + MANUAL = 1 + AUTO = 2 + +class ActTypeConsumeCredit(IntEnum): + PurchaseTicketA1 = 1 + PurchaseTicketA2 = 2 + PurchaseTicketA6 = 3 + PurchaseTicketB1 = 4 + PurchaseTicketB2 = 5 + PurchaseTicketB7 = 6 + PurchaseTicketC1 = 7 + PurchaseTicketC3 = 8 + PurchaseTicketC8 = 9 + PurchaseTicketD1 = 10 + PurchaseTicketD2 = 11 + PurchaseTicketD6 = 12 + PurchaseTicketE1 = 13 + PurchaseTicketE3 = 14 + PurchaseTicketE8 = 15 + PurchaseTicketF1 = 16 + PurchaseTicketF2 = 17 + PurchaseTicketF6 = 18 + QuestContinue = 21 + QuestRarityUp = 22 + GashaSatelite = 31 + GashaSateliteAdd1 = 32 + GashaSateliteAdd2 = 33 + GashaTerminal = 41 + GashaTerminalAdd1 = 42 + GashaTerminalAdd2 = 43 + ResourceDischarge = 44 + ResourceHolo = 45 + ProfileCardDischarge = 46 + ProfileCardHolo = 47 + +class ProtocolErrorNum(IntEnum): # header error_num field + SUCCESS = 0 + + ALREADY_LOGIN_DIFF = 1301 + ALREADY_LOGIN_SAME = 1302 + INVALID_AUTH_TYPE = 1303 + INVALID_CABINET_TYPE = 1304 + AMID_SERVER_CONNECT_ERROR = 1305 + AMID_INFO_REQUEST_ERROR = 1306 + NOT_EXIST_PLAY_DATA = 1307 + HAVE_NEVER_PLAYED_SATELLITE = 1308 + + RESOURCE_CARD_ERR1 = 4831 # ScanQRQuestProfileCard + RESOURCE_CARD_NOT_EXIST = 4832 + RESOURCE_CARD_ERR3 = 4833 # ScanQRQuestResourceCard / ScanQRShopResourceCard + RESOURCE_CARD_ERR6 = 4836 # ScanQRQuestResourceCard / ScanQRShopResourceCard + RESOURCE_CARD_ERR7 = 4837 # ScanQRQuestResourceCard / ScanQRShopResourceCard + RESOURCE_CARD_ERR8 = 4838 # ScanQRShopResourceCard + RESOURCE_CARD_ERR9 = 4839 # ScanQRQuestProfileCard + + CREDIT_GASHA_ERROR = 7111 + + PURCHASE_ERROR = 7711 + + PAYING_PLAY_END_ERROR = 9120 + + CODE_ANALYSIS_API_NOT_RESPONSE = 9201 + CODE_ANALYSIS_API_INIQUITY_SERIAL_CODE = 9202 + CODE_USE_API_USED_SERIAL_CODE = 9203 + CODE_USE_API_NOT_RESPONSE = 9206 + CODE_USE_API_NG = 9207 + CODE_USE_API_LOCK = 9208 + CODE_USE_API_MAINTENANCE = 9209 + CODE_ANALYSIS_API_NOT_MASTER_DATA = 9210 + CODE_ANALYSIS_API_EXPIRED_SERIAL_CODE = 9211 + +class ProtocolResult(IntEnum): # result field, if used + NONE = -1 + FAILED = 0 + SUCCESS = 1 + PARAM_ERROR = 2 + MAINTENANCE = 3 + CONNECT_ERROR = 99 + class BaseHelper: def __init__(self, data: bytes, offset: int) -> None: self._sz = 0 @@ -143,9 +291,10 @@ class BaseHelper: def decode_arr_cls(data: bytes, offset: int, cls: BaseHelper): size = 0 num_cls = decode_int(data, offset + size) + size += INT_OFF cls_type = type(cls) - ret: List[cls_type] = [] + ret: List[cls_type] = [] # type: ignore for _ in range(num_cls): tmp = cls(data, offset + size) size += tmp.get_size() @@ -171,65 +320,1303 @@ class MaterialCommonRewardUserData(BaseHelper): self.user_common_reward_id = user_common_reward_id[0] self._sz += user_common_reward_id[1] +class QuestScenePlayEndUnanalyzedLogRewardData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.unanalyzed_log_grade_id = decode_int(data, off) + off += INT_OFF + + self.common_reward_data, new_off = decode_arr_cls(data, off, CommonRewardData) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, grade: int, reward: Dict) -> "QuestScenePlayEndUnanalyzedLogRewardData": + ret = cls(b"\x00" * 8, 0) + ret.unanalyzed_log_grade_id = grade + ret.common_reward_data = [CommonRewardData.from_args(reward.get('CommonRewardType'), reward.get('CommonRewardId'), reward.get('CommonRewardNum'))] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.unanalyzed_log_grade_id) \ + + encode_arr_cls(self.common_reward_data) + class PartyTeamData(BaseHelper): - def __init__(self, data: bytes, offset: int) -> None: - sz = 0 - user_party_team_id = decode_str(data, offset + sz) - self.user_party_team_id = user_party_team_id[0] - sz += user_party_team_id[1] + def __init__(self, data: bytes, offset: int): + off = offset + self.user_party_team_id, new_off = decode_str(data, off) + off += new_off - self.arrangement_num = decode_byte(data, offset + sz) - sz += BYTE_OFF + self.arrangement_num = decode_byte(data, off) + off += BYTE_OFF - user_hero_log_id = decode_str(data, offset + sz) - self.user_hero_log_id = user_hero_log_id[0] - sz += user_hero_log_id[1] + self.user_hero_log_id, new_off = decode_str(data, off) + off += new_off - main_weapon_user_equipment_id = decode_str(data, offset + sz) - self.main_weapon_user_equipment_id = main_weapon_user_equipment_id[0] - sz += main_weapon_user_equipment_id[1] + self.main_weapon_user_equipment_id, new_off = decode_str(data, off) + off += new_off - sub_equipment_user_equipment_id = decode_str(data, offset + sz) - self.sub_equipment_user_equipment_id = sub_equipment_user_equipment_id[0] - sz += sub_equipment_user_equipment_id[1] + self.sub_equipment_user_equipment_id, new_off = decode_str(data, off) + off += new_off - self.skill_slot1_skill_id = decode_int(data, offset + sz) - sz += INT_OFF + self.skill_slot1_skill_id = decode_int(data, off) + off += INT_OFF - self.skill_slot2_skill_id = decode_int(data, offset + sz) - sz += INT_OFF + self.skill_slot2_skill_id = decode_int(data, off) + off += INT_OFF - self.skill_slot3_skill_id = decode_int(data, offset + sz) - sz += INT_OFF + self.skill_slot3_skill_id = decode_int(data, off) + off += INT_OFF - self.skill_slot4_skill_id = decode_int(data, offset + sz) - sz += INT_OFF + self.skill_slot4_skill_id = decode_int(data, off) + off += INT_OFF - self.skill_slot5_skill_id = decode_int(data, offset + sz) - sz += INT_OFF + self.skill_slot5_skill_id = decode_int(data, off) + off += INT_OFF - self._sz = sz + self._sz = off - offset + + @classmethod + def from_args(cls, party_team_id: str, arr_num: int, data: Dict) -> "PartyTeamData": + ret = cls(b"\x00" * 37, 0) + ret.user_party_team_id = party_team_id + ret.arrangement_num = arr_num + ret.user_hero_log_id = data['id'] + ret.main_weapon_user_equipment_id = data['main_weapon'] + ret.sub_equipment_user_equipment_id = data['sub_equipment'] + ret.skill_slot1_skill_id = data['skill_slot1_skill_id'] + ret.skill_slot2_skill_id = data['skill_slot2_skill_id'] + ret.skill_slot3_skill_id = data['skill_slot3_skill_id'] + ret.skill_slot4_skill_id = data['skill_slot4_skill_id'] + ret.skill_slot5_skill_id = data['skill_slot5_skill_id'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_str(self.user_party_team_id) \ + + encode_byte(self.arrangement_num) \ + + encode_str(self.user_hero_log_id) \ + + encode_str(self.main_weapon_user_equipment_id) \ + + encode_str(self.sub_equipment_user_equipment_id) \ + + encode_int(self.skill_slot1_skill_id) \ + + encode_int(self.skill_slot2_skill_id) \ + + encode_int(self.skill_slot3_skill_id) \ + + encode_int(self.skill_slot4_skill_id) \ + + encode_int(self.skill_slot5_skill_id) + +class CommonRewardData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.common_reward_type = decode_short(data, off) + off += SHORT_OFF + + self.common_reward_id = decode_int(data, off) + off += INT_OFF + + self.common_reward_num = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, reward_type: int, reward_id: int, reward_num: int) -> "CommonRewardData": + ret = cls(b"\x00" * 10, 0) + ret.common_reward_type = reward_type + ret.common_reward_id = reward_id + ret.common_reward_num = reward_num + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.common_reward_type) \ + + encode_int(self.common_reward_id) \ + + encode_int(self.common_reward_num) + +class ReadProfileCardData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.profile_card_code, new_off = decode_str(data, off) + off += new_off + + self.nick_name, new_off = decode_str(data, off) + off += new_off + + self.rank_num = decode_short(data, off) + off += SHORT_OFF + + self.setting_title_id = decode_int(data, off) + off += INT_OFF + + self.skill_id = decode_short(data, off) + off += SHORT_OFF + + self.hero_log_hero_log_id = decode_int(data, off) + off += INT_OFF + + self.hero_log_log_level = decode_short(data, off) + off += SHORT_OFF + + self.hero_log_awakening_stage = decode_short(data, off) + off += SHORT_OFF + + self.hero_log_property1_property_id = decode_int(data, off) + off += INT_OFF + + self.hero_log_property1_value1 = decode_int(data, off) + off += INT_OFF + + self.hero_log_property1_value2 = decode_int(data, off) + off += INT_OFF + + self.hero_log_property2_property_id = decode_int(data, off) + off += INT_OFF + + self.hero_log_property2_value1 = decode_int(data, off) + off += INT_OFF + + self.hero_log_property2_value2 = decode_int(data, off) + off += INT_OFF + + self.hero_log_property3_property_id = decode_int(data, off) + off += INT_OFF + + self.hero_log_property3_value1 = decode_int(data, off) + off += INT_OFF + + self.hero_log_property3_value2 = decode_int(data, off) + off += INT_OFF + + self.hero_log_property4_property_id = decode_int(data, off) + off += INT_OFF + + self.hero_log_property4_value1 = decode_int(data, off) + off += INT_OFF + + self.hero_log_property4_value2 = decode_int(data, off) + off += INT_OFF + + self.main_weapon_equipment_id = decode_int(data, off) + off += INT_OFF + + self.main_weapon_enhancement_value = decode_short(data, off) + off += SHORT_OFF + + self.main_weapon_awakening_stage = decode_short(data, off) + off += SHORT_OFF + + self.main_weapon_property1_property_id = decode_int(data, off) + off += INT_OFF + + self.main_weapon_property1_value1 = decode_int(data, off) + off += INT_OFF + + self.main_weapon_property1_value2 = decode_int(data, off) + off += INT_OFF + + self.main_weapon_property2_property_id = decode_int(data, off) + off += INT_OFF + + self.main_weapon_property2_value1 = decode_int(data, off) + off += INT_OFF + + self.main_weapon_property2_value2 = decode_int(data, off) + off += INT_OFF + + self.main_weapon_property3_property_id = decode_int(data, off) + off += INT_OFF + + self.main_weapon_property3_value1 = decode_int(data, off) + off += INT_OFF + + self.main_weapon_property3_value2 = decode_int(data, off) + off += INT_OFF + + self.main_weapon_property4_property_id = decode_int(data, off) + off += INT_OFF + + self.main_weapon_property4_value1 = decode_int(data, off) + off += INT_OFF + + self.main_weapon_property4_value2 = decode_int(data, off) + off += INT_OFF + + self.sub_equipment_equipment_id = decode_int(data, off) + off += INT_OFF + + self.sub_equipment_enhancement_value = decode_short(data, off) + off += SHORT_OFF + + self.sub_equipment_awakening_stage = decode_short(data, off) + off += SHORT_OFF + + self.sub_equipment_property1_property_id = decode_int(data, off) + off += INT_OFF + + self.sub_equipment_property1_value1 = decode_int(data, off) + off += INT_OFF + + self.sub_equipment_property1_value2 = decode_int(data, off) + off += INT_OFF + + self.sub_equipment_property2_property_id = decode_int(data, off) + off += INT_OFF + + self.sub_equipment_property2_value1 = decode_int(data, off) + off += INT_OFF + + self.sub_equipment_property2_value2 = decode_int(data, off) + off += INT_OFF + + self.sub_equipment_property3_property_id = decode_int(data, off) + off += INT_OFF + + self.sub_equipment_property3_value1 = decode_int(data, off) + off += INT_OFF + + self.sub_equipment_property3_value2 = decode_int(data, off) + off += INT_OFF + + self.sub_equipment_property4_property_id = decode_int(data, off) + off += INT_OFF + + self.sub_equipment_property4_value1 = decode_int(data, off) + off += INT_OFF + + self.sub_equipment_property4_value2 = decode_int(data, off) + off += INT_OFF + + self.holographic_flag = decode_byte(data, off) + off += BYTE_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, user_profile: Dict, hero_data: Dict) -> "ReadProfileCardData": + ret = cls(b"\x00" * 185, 0) + # TODO: real data + ret.profile_card_code = "" + ret.nick_name = "" + ret.rank_num = 0 + ret.setting_title_id = 0 + ret.skill_id = 0 + ret.hero_log_hero_log_id = 0 + ret.hero_log_log_level = 0 + ret.hero_log_awakening_stage = 0 + ret.hero_log_property1_property_id = 0 + ret.hero_log_property1_value1 = 0 + ret.hero_log_property1_value2 = 0 + ret.hero_log_property2_property_id = 0 + ret.hero_log_property2_value1 = 0 + ret.hero_log_property2_value2 = 0 + ret.hero_log_property3_property_id = 0 + ret.hero_log_property3_value1 = 0 + ret.hero_log_property3_value2 = 0 + ret.hero_log_property4_property_id = 0 + ret.hero_log_property4_value1 = 0 + ret.hero_log_property4_value2 = 0 + ret.main_weapon_equipment_id = 0 + ret.main_weapon_enhancement_value = 0 + ret.main_weapon_awakening_stage = 0 + ret.main_weapon_property1_property_id = 0 + ret.main_weapon_property1_value1 = 0 + ret.main_weapon_property1_value2 = 0 + ret.main_weapon_property2_property_id = 0 + ret.main_weapon_property2_value1 = 0 + ret.main_weapon_property2_value2 = 0 + ret.main_weapon_property3_property_id = 0 + ret.main_weapon_property3_value1 = 0 + ret.main_weapon_property3_value2 = 0 + ret.main_weapon_property4_property_id = 0 + ret.main_weapon_property4_value1 = 0 + ret.main_weapon_property4_value2 = 0 + ret.sub_equipment_equipment_id = 0 + ret.sub_equipment_enhancement_value = 0 + ret.sub_equipment_awakening_stage = 0 + ret.sub_equipment_property1_property_id = 0 + ret.sub_equipment_property1_value1 = 0 + ret.sub_equipment_property1_value2 = 0 + ret.sub_equipment_property2_property_id = 0 + ret.sub_equipment_property2_value1 = 0 + ret.sub_equipment_property2_value2 = 0 + ret.sub_equipment_property3_property_id = 0 + ret.sub_equipment_property3_value1 = 0 + ret.sub_equipment_property3_value2 = 0 + ret.sub_equipment_property4_property_id = 0 + ret.sub_equipment_property4_value1 = 0 + ret.sub_equipment_property4_value2 = 0 + ret.holographic_flag = 0 + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_str(self.profile_card_code) \ + + encode_str(self.nick_name) \ + + encode_short(self.rank_num) \ + + encode_int(self.setting_title_id) \ + + encode_short(self.skill_id) \ + + encode_int(self.hero_log_hero_log_id) \ + + encode_short(self.hero_log_log_level) \ + + encode_short(self.hero_log_awakening_stage) \ + + encode_int(self.hero_log_property1_property_id) \ + + encode_int(self.hero_log_property1_value1) \ + + encode_int(self.hero_log_property1_value2) \ + + encode_int(self.hero_log_property2_property_id) \ + + encode_int(self.hero_log_property2_value1) \ + + encode_int(self.hero_log_property2_value2) \ + + encode_int(self.hero_log_property3_property_id) \ + + encode_int(self.hero_log_property3_value1) \ + + encode_int(self.hero_log_property3_value2) \ + + encode_int(self.hero_log_property4_property_id) \ + + encode_int(self.hero_log_property4_value1) \ + + encode_int(self.hero_log_property4_value2) \ + + encode_int(self.main_weapon_equipment_id) \ + + encode_short(self.main_weapon_enhancement_value) \ + + encode_short(self.main_weapon_awakening_stage) \ + + encode_int(self.main_weapon_property1_property_id) \ + + encode_int(self.main_weapon_property1_value1) \ + + encode_int(self.main_weapon_property1_value2) \ + + encode_int(self.main_weapon_property2_property_id) \ + + encode_int(self.main_weapon_property2_value1) \ + + encode_int(self.main_weapon_property2_value2) \ + + encode_int(self.main_weapon_property3_property_id) \ + + encode_int(self.main_weapon_property3_value1) \ + + encode_int(self.main_weapon_property3_value2) \ + + encode_int(self.main_weapon_property4_property_id) \ + + encode_int(self.main_weapon_property4_value1) \ + + encode_int(self.main_weapon_property4_value2) \ + + encode_int(self.sub_equipment_equipment_id) \ + + encode_short(self.sub_equipment_enhancement_value) \ + + encode_short(self.sub_equipment_awakening_stage) \ + + encode_int(self.sub_equipment_property1_property_id) \ + + encode_int(self.sub_equipment_property1_value1) \ + + encode_int(self.sub_equipment_property1_value2) \ + + encode_int(self.sub_equipment_property2_property_id) \ + + encode_int(self.sub_equipment_property2_value1) \ + + encode_int(self.sub_equipment_property2_value2) \ + + encode_int(self.sub_equipment_property3_property_id) \ + + encode_int(self.sub_equipment_property3_value1) \ + + encode_int(self.sub_equipment_property3_value2) \ + + encode_int(self.sub_equipment_property4_property_id) \ + + encode_int(self.sub_equipment_property4_value1) \ + + encode_int(self.sub_equipment_property4_value2) \ + + encode_byte(self.holographic_flag) + +class QuestSceneBestScoreUserData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.clear_time = decode_int(data, off) + off += INT_OFF + + self.combo_num = decode_int(data, off) + off += INT_OFF + + self.total_damage, new_off = decode_str(data, off) + off += new_off + + self.concurrent_destroying_num = decode_short(data, off) + off += SHORT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "QuestSceneBestScoreUserData": + ret = cls(b"\x00" * 14, 0) + ret.clear_time = data['clear_time'] + ret.combo_num = data['combo_num'] + ret.total_damage = data['total_damage'] + ret.concurrent_destroying_num = data['concurrent_destroying_num'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.clear_time) \ + + encode_int(self.combo_num) \ + + encode_str(self.total_damage) \ + + encode_short(self.concurrent_destroying_num) + +class QuestSceneExBonusUserData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.ex_bonus_table_id = decode_int(data, off) + off += INT_OFF + + self.achievement_flag = decode_byte(data, off) + off += BYTE_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, table_id: int = 0, ach_flag: bool = False) -> "QuestSceneExBonusUserData": + ret = cls(b"\x00" * 5, 0) + ret.ex_bonus_table_id = table_id + ret.achievement_flag = ach_flag + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.ex_bonus_table_id) \ + + encode_byte(self.achievement_flag) + +class QuestSceneUserData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.quest_type = decode_byte(data, off) + off += BYTE_OFF + + self.quest_scene_id = decode_short(data, off) + off += SHORT_OFF + + self.clear_flag = decode_byte(data, off) + off += BYTE_OFF + + self.quest_scene_best_score_user_data: List[QuestSceneBestScoreUserData] = [] + self.quest_scene_best_score_user_data, new_off = decode_arr_cls(data, off, QuestSceneBestScoreUserData) + off += new_off + + self.quest_scene_ex_bonus_user_data_list: List[QuestSceneExBonusUserData] = [] + self.quest_scene_ex_bonus_user_data_list, new_off = decode_arr_cls(data, off, QuestSceneExBonusUserData) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "QuestSceneUserData": + ret = cls(b"\x00" * 12, 0) + ret.quest_type = data['quest_type'] + ret.quest_scene_id = data['quest_scene_id'] + ret.clear_flag = data['quest_clear_flag'] + ret.quest_scene_best_score_user_data = [QuestSceneBestScoreUserData.from_args(data)] + ret.quest_scene_ex_bonus_user_data_list = [] # TODO + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.quest_type) \ + + encode_short(self.quest_scene_id) \ + + encode_byte(self.clear_flag) \ + + encode_arr_cls(self.quest_scene_best_score_user_data) \ + + encode_arr_cls(self.quest_scene_ex_bonus_user_data_list) + +class QuestScenePlayStartAppearancePlayerTraceData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.user_quest_scene_player_trace_id, new_off = decode_str(data, off) + off += new_off + + self.nick_name, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, sesh_id:int, nickname: str) -> "QuestScenePlayStartAppearancePlayerTraceData": + ret = cls(b"\x00" * 8, 0) + ret.user_quest_scene_player_trace_id = str(sesh_id) + ret.nick_name = nickname + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_str(self.user_quest_scene_player_trace_id) \ + + encode_str(self.nick_name) + +class QuestScenePlayStartResponseData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.appearance_player_trace_data_list, new_off = decode_arr_cls(data, off, QuestScenePlayStartAppearancePlayerTraceData) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, sesh_id: int, nickname: str) -> "QuestScenePlayStartResponseData": + ret = cls(b"\x00" * 99, 0) + ret.appearance_player_trace_data_list = [QuestScenePlayStartAppearancePlayerTraceData.from_args(sesh_id, nickname)] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_arr_cls(self.appearance_player_trace_data_list) + +class QuestScenePlayStartRequestData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.user_party_id, new_off = decode_str(data, off) + off += new_off + + self.appoint_leader_resource_card_code, new_off = decode_str(data, off) + off += new_off + + self.use_profile_card_code, new_off = decode_str(data, off) + off += new_off + + self.quest_drop_boost_apply_flag = decode_byte(data, off) + off += BYTE_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "QuestScenePlayStartRequestData": + ret = cls(b"\x00" * 13, 0) + ret.user_party_id = data['UserPartyId'] + ret.appoint_leader_resource_card_code = data['AppointLeaderResourceCardCode'] + ret.use_profile_card_code = data['UseProfileCardCode'] + ret.quest_drop_boost_apply_flag = data['QuestDropBoostApplyFlag'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_str(self.user_party_id) \ + + encode_str(self.appoint_leader_resource_card_code) \ + + encode_str(self.use_profile_card_code) \ + + encode_byte(self.quest_drop_boost_apply_flag) + +class QuestSceneMultiPlayStartEntryUserData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.store_id, new_off = decode_str(data, off) + off += new_off + + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.host_flag = decode_byte(data, off) + off += BYTE_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "QuestSceneMultiPlayStartEntryUserData": + ret = cls(b"\x00" * 9, 0) + ret.store_id = data['StoreId'] + ret.user_id = data['UserId'] + ret.host_flag = data['HostFlag'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_str(self.store_id) \ + + encode_str(self.user_id) \ + + encode_byte(self.host_flag) + +class QuestSceneMultiPlayStartRequestData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.room_id, new_off = decode_str(data, off) + off += new_off + + self.matching_mode = decode_byte(data, off) + off += BYTE_OFF + + self.entry_user_data_list: List[QuestSceneMultiPlayStartEntryUserData] = [] + self.entry_user_data_list, new_off = decode_arr_cls(data, off, QuestSceneMultiPlayStartEntryUserData) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "QuestSceneMultiPlayStartRequestData": + ret = cls(b"\x00" * 9, 0) + ret.room_id = data['RoomId'] + ret.matching_mode = data['MatchingMode'] + ret.entry_user_data_list = [] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_str(self.room_id) \ + + encode_byte(self.matching_mode) \ + + encode_arr_cls(self.entry_user_data_list) + +class QuestSceneMultiPlayStartResponseData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.dummy_1 = decode_byte(data, off) + off += BYTE_OFF + + self.dummy_2 = decode_byte(data, off) + off += BYTE_OFF + + self.dummy_3 = decode_byte(data, off) + off += BYTE_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls) -> "QuestSceneMultiPlayStartResponseData": + ret = cls(b"\x00" * 3, 0) + ret.dummy_1 = 0 + ret.dummy_2 = 0 + ret.dummy_3 = 0 + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.dummy_1) \ + + encode_byte(self.dummy_2) \ + + encode_byte(self.dummy_3) + +class QuestSceneMultiPlayEndResponseData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.dummy_1 = decode_byte(data, off) + off += BYTE_OFF + + self.dummy_2 = decode_byte(data, off) + off += BYTE_OFF + + self.dummy_3 = decode_byte(data, off) + off += BYTE_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls) -> "QuestSceneMultiPlayEndResponseData": + ret = cls(b"\x00" * 3, 0) + ret.dummy_1 = 0 + ret.dummy_2 = 0 + ret.dummy_3 = 0 + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.dummy_1) \ + + encode_byte(self.dummy_2) \ + + encode_byte(self.dummy_3) + +class QuestScenePlayEndBaseGetData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.get_hero_log_exp = decode_int(data, off) + off += INT_OFF + + self.get_col = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "QuestScenePlayEndBaseGetData": + ret = cls(b"\x00" * 99, 0) + ret.get_hero_log_exp = data['GetHeroLogExp'] + ret.get_col = data['GetCol'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.get_hero_log_exp) \ + + encode_int(self.get_col) + +class QuestScenePlayEndDiscoveryEnemyData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.enemy_kind_id = decode_int(data, off) + off += INT_OFF + + self.destroy_num = decode_short(data, off) + off += SHORT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "QuestScenePlayEndDiscoveryEnemyData": + ret = cls(b"\x00" * 99, 0) + ret.enemy_kind_id = data['EnemyKindId'] + ret.destroy_num = data['DestroyNum'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.enemy_kind_id) \ + + encode_short(self.destroy_num) + +class QuestScenePlayEndGetPlayerTraceData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.user_quest_scene_player_trace_id, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "QuestScenePlayEndGetPlayerTraceData": + ret = cls(b"\x00" * 99, 0) + ret.user_quest_scene_player_trace_id = data['UserQuestScenePlayerTraceId'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_str(self.user_quest_scene_player_trace_id) + +class QuestScenePlayEndGetRareDropData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.quest_rare_drop_id = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "QuestScenePlayEndGetRareDropData": + ret = cls(b"\x00" * 99, 0) + ret.quest_rare_drop_id = data['QuestRareDropId'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.quest_rare_drop_id) + +class QuestScenePlayEndGetSpecialRareDropData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.quest_special_rare_drop_id = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "QuestScenePlayEndGetSpecialRareDropData": + ret = cls(b"\x00" * 99, 0) + ret.quest_special_rare_drop_id = data['QuestSpecialRareDropId'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.quest_special_rare_drop_id) + +class QuestScenePlayEndGetUnanalyzedLogTmpRewardData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.unanalyzed_log_grade_id = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "QuestScenePlayEndGetUnanalyzedLogTmpRewardData": + ret = cls(b"\x00" * 99, 0) + ret.unanalyzed_log_grade_id = data['UnanalyzedLogGradeId'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.unanalyzed_log_grade_id) + +class QuestScenePlayEndScoreData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.clear_time = decode_int(data, off) + off += INT_OFF + + self.combo_num = decode_int(data, off) + off += INT_OFF + + self.total_damage, new_off = decode_str(data, off) + off += new_off + + self.concurrent_destroying_num = decode_short(data, off) + off += SHORT_OFF + + self.reaching_skill_level = decode_short(data, off) + off += SHORT_OFF + + self.ko_chara_num = decode_byte(data, off) + off += BYTE_OFF + + self.acceleration_invocation_num = decode_short(data, off) + off += SHORT_OFF + + self.boss_destroying_num = decode_short(data, off) + off += SHORT_OFF + + self.synchro_skill_used_flag = decode_byte(data, off) + off += BYTE_OFF + + self.used_friend_skill_id = decode_int(data, off) + off += INT_OFF + + self.friend_skill_used_flag = decode_byte(data, off) + off += BYTE_OFF + + self.continue_cnt = decode_short(data, off) + off += SHORT_OFF + + self.total_loss_num = decode_short(data, off) + off += SHORT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "QuestScenePlayEndScoreData": + ret = cls(b"\x00" * 99, 0) + ret.clear_time = data['ClearTime'] + ret.combo_num = data['ComboNum'] + ret.total_damage = data['TotalDamage'] + ret.concurrent_destroying_num = data['ConcurrentDestroyingNum'] + ret.reaching_skill_level = data['ReachingSkillLevel'] + ret.ko_chara_num = data['KoCharaNum'] + ret.acceleration_invocation_num = data['AccelerationInvocationNum'] + ret.boss_destroying_num = data['BossDestroyingNum'] + ret.synchro_skill_used_flag = data['SynchroSkillUsedFlag'] + ret.used_friend_skill_id = data['UsedFriendSkillId'] + ret.friend_skill_used_flag = data['FriendSkillUsedFlag'] + ret.continue_cnt = data['ContinueCnt'] + ret.total_loss_num = data['TotalLossNum'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.clear_time) \ + + encode_int(self.combo_num) \ + + encode_str(self.total_damage) \ + + encode_short(self.concurrent_destroying_num) \ + + encode_short(self.reaching_skill_level) \ + + encode_byte(self.ko_chara_num) \ + + encode_short(self.acceleration_invocation_num) \ + + encode_short(self.boss_destroying_num) \ + + encode_byte(self.synchro_skill_used_flag) \ + + encode_int(self.used_friend_skill_id) \ + + encode_byte(self.friend_skill_used_flag) \ + + encode_short(self.continue_cnt) \ + + encode_short(self.total_loss_num) + +class QuestScenePlayEndMissionData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.mission_id = decode_int(data, off) + off += INT_OFF + + self.clear_flag = decode_byte(data, off) + off += BYTE_OFF + + self.mission_difficulty_id = decode_short(data, off) + off += SHORT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "QuestScenePlayEndMissionData": + ret = cls(b"\x00" * 99, 0) + ret.mission_id = data['MissionId'] + ret.clear_flag = data['ClearFlag'] + ret.mission_difficulty_id = data['MissionDifficultyId'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.mission_id) \ + + encode_byte(self.clear_flag) \ + + encode_short(self.mission_difficulty_id) + +class QuestScenePlayEndDestroyBossData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.boss_type = decode_byte(data, off) + off += BYTE_OFF + + self.enemy_kind_id = decode_int(data, off) + off += INT_OFF + + self.destroy_num = decode_short(data, off) + off += SHORT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "QuestScenePlayEndDestroyBossData": + ret = cls(b"\x00" * 99, 0) + ret.boss_type = data['BossType'] + ret.enemy_kind_id = data['EnemyKindId'] + ret.destroy_num = data['DestroyNum'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.boss_type) \ + + encode_int(self.enemy_kind_id) \ + + encode_short(self.destroy_num) + +class QuestScenePlayEndGetEventItemData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.event_item_id = decode_int(data, off) + off += INT_OFF + + self.get_num = decode_short(data, off) + off += SHORT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "QuestScenePlayEndGetEventItemData": + ret = cls(b"\x00" * 99, 0) + ret.event_item_id = data['EventItemId'] + ret.get_num = data['GetNum'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.event_item_id) \ + + encode_short(self.get_num) + +class QuestTreasureHuntPlayEndResponseData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.get_event_point = decode_int(data, off) + off += INT_OFF + + self.total_event_point = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, get_pt: int = 0, total_pt: int = 0) -> "QuestTreasureHuntPlayEndResponseData": + ret = cls(b"\x00" * 8, 0) + ret.get_event_point = get_pt + ret.total_event_point = total_pt + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.get_event_point) \ + + encode_int(self.total_event_point) + +class QuestTrialTowerPlayEndUpdatedNotificationData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.store_best_score_clear_time_flag = decode_byte(data, off) + off += BYTE_OFF + + self.store_best_score_combo_num_flag = decode_byte(data, off) + off += BYTE_OFF + + self.store_best_score_total_damage_flag = decode_byte(data, off) + off += BYTE_OFF + + self.store_best_score_concurrent_destroying_num_flag = decode_byte(data, off) + off += BYTE_OFF + + self.store_reaching_trial_tower_rank = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, shop_best_time: bool = False, shop_best_combo: bool = False, shop_best_damage: bool = False, shop_best_concurrent: bool = False, shop_tower_rank: int = 0) -> "QuestTrialTowerPlayEndUpdatedNotificationData": + ret = cls(b"\x00" * 8, 0) + ret.store_best_score_clear_time_flag = shop_best_time + ret.store_best_score_combo_num_flag = shop_best_combo + ret.store_best_score_total_damage_flag = shop_best_damage + ret.store_best_score_concurrent_destroying_num_flag = shop_best_concurrent + ret.store_reaching_trial_tower_rank = shop_tower_rank + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.store_best_score_clear_time_flag) \ + + encode_byte(self.store_best_score_combo_num_flag) \ + + encode_byte(self.store_best_score_total_damage_flag) \ + + encode_byte(self.store_best_score_concurrent_destroying_num_flag) \ + + encode_int(self.store_reaching_trial_tower_rank) + +class CommonRewardUserData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.common_reward_type = decode_short(data, off) + off += SHORT_OFF + + self.user_common_reward_id, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "CommonRewardUserData": + ret = cls(b"\x00" * 99, 0) + ret.common_reward_type = data['CommonRewardType'] + ret.user_common_reward_id = data['UserCommonRewardId'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.common_reward_type) \ + + encode_str(self.user_common_reward_id) + +class QuestScenePlayEndResponseData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.rarity_up_occurrence_flag = decode_byte(data, off) + off += BYTE_OFF + + self.adventure_ex_area_occurrences_flag = decode_byte(data, off) + off += BYTE_OFF + + self.ex_bonus_data_list: List[QuestScenePlayEndExBonusData] = [] + self.ex_bonus_data_list, new_off = decode_arr_cls(data, off, QuestScenePlayEndExBonusData) + off += new_off + + self.play_end_player_trace_reward_data_list: List[QuestScenePlayEndPlayerTraceRewardData] = [] + self.play_end_player_trace_reward_data_list, new_off = decode_arr_cls(data, off, QuestScenePlayEndPlayerTraceRewardData) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, rarity_up_flag: bool = False, adventure_ex_area_flg: bool = False, ex_tables: List = []) -> "QuestScenePlayEndResponseData": + ret = cls(b"\x00" * 99, 0) + ret.rarity_up_occurrence_flag = rarity_up_flag + ret.adventure_ex_area_occurrences_flag = adventure_ex_area_flg + for x in ex_tables: + ret.ex_bonus_data_list.append(QuestScenePlayEndExBonusData.from_args(x['table'], x['ach_status'])) + ret.play_end_player_trace_reward_data_list = [] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.rarity_up_occurrence_flag) \ + + encode_byte(self.adventure_ex_area_occurrences_flag) \ + + encode_arr_cls(self.ex_bonus_data_list) \ + + encode_arr_cls(self.play_end_player_trace_reward_data_list) + +class QuestScenePlayEndExBonusData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.ex_bonus_table_id = decode_int(data, off) + off += INT_OFF + + self.achievement_status = decode_byte(data, off) + off += BYTE_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, table_id: int, ach_status: int) -> "QuestScenePlayEndExBonusData": + ret = cls(b"\x00" * 5, 0) + ret.ex_bonus_table_id = table_id + ret.achievement_status = ach_status + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.ex_bonus_table_id) \ + + encode_byte(self.achievement_status) + +class QuestScenePlayEndPlayerTraceRewardData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.common_reward_data: List[CommonRewardData] + self.common_reward_data, new_off = decode_arr_cls(data, off, CommonRewardData) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, reward_data: Dict) -> "QuestScenePlayEndPlayerTraceRewardData": + ret = cls(b"\x00" * 4, 0) + ret.common_reward_data.append(CommonRewardData.from_args(reward_data['CommonRewardType'], reward_data['CommonRewardId'], reward_data['CommonRewardNum'])) + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_arr_cls(self.common_reward_data) + +class QuestScenePlayEndRequestData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.play_result_flag = decode_byte(data, off) + off += BYTE_OFF + + self.base_get_data: List[QuestScenePlayEndBaseGetData] = [] + self.base_get_data, new_off = decode_arr_cls(data, off, QuestScenePlayEndBaseGetData) + off += new_off + + self.get_player_trace_data_list: List[QuestScenePlayEndGetPlayerTraceData] = [] + self.get_player_trace_data_list, new_off = decode_arr_cls(data, off, QuestScenePlayEndGetPlayerTraceData) + off += new_off + + self.get_rare_drop_data_list: List[QuestScenePlayEndGetRareDropData] = [] + self.get_rare_drop_data_list, new_off = decode_arr_cls(data, off, QuestScenePlayEndGetRareDropData) + off += new_off + + self.get_special_rare_drop_data_list: List[QuestScenePlayEndGetSpecialRareDropData] = [] + self.get_special_rare_drop_data_list, new_off = decode_arr_cls(data, off, QuestScenePlayEndGetSpecialRareDropData) + off += new_off + + self.get_unanalyzed_log_tmp_reward_data_list: List[QuestScenePlayEndGetUnanalyzedLogTmpRewardData] = [] + self.get_unanalyzed_log_tmp_reward_data_list, new_off = decode_arr_cls(data, off, QuestScenePlayEndGetUnanalyzedLogTmpRewardData) + off += new_off + + self.get_event_item_data_list: List[QuestScenePlayEndGetEventItemData] = [] + self.get_event_item_data_list, new_off = decode_arr_cls(data, off, QuestScenePlayEndGetEventItemData) + off += new_off + + self.discovery_enemy_data_list: List[QuestScenePlayEndDiscoveryEnemyData] = [] + self.discovery_enemy_data_list, new_off = decode_arr_cls(data, off, QuestScenePlayEndDiscoveryEnemyData) + off += new_off + + self.destroy_boss_data_list: List[QuestScenePlayEndDestroyBossData] = [] + self.destroy_boss_data_list, new_off = decode_arr_cls(data, off, QuestScenePlayEndDestroyBossData) + off += new_off + + self.mission_data_list: List[QuestScenePlayEndMissionData] = [] + self.mission_data_list, new_off = decode_arr_cls(data, off, QuestScenePlayEndMissionData) + off += new_off + + self.score_data: List[QuestScenePlayEndScoreData] = [] + self.score_data, new_off = decode_arr_cls(data, off, QuestScenePlayEndScoreData) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "QuestScenePlayEndRequestData": + ret = cls(b"\x00" * 99, 0) + ret.play_result_flag = data['PlayResultFlag'] + ret.base_get_data = [] + ret.get_player_trace_data_list = [] + ret.get_rare_drop_data_list = [] + ret.get_special_rare_drop_data_list = [] + ret.get_unanalyzed_log_tmp_reward_data_list = [] + ret.get_event_item_data_list = [] + ret.discovery_enemy_data_list = [] + ret.destroy_boss_data_list = [] + ret.mission_data_list = [] + ret.score_data = [] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.play_result_flag) \ + + encode_arr_cls(self.base_get_data) \ + + encode_arr_cls(self.get_player_trace_data_list) \ + + encode_arr_cls(self.get_rare_drop_data_list) \ + + encode_arr_cls(self.get_special_rare_drop_data_list) \ + + encode_arr_cls(self.get_unanalyzed_log_tmp_reward_data_list) \ + + encode_arr_cls(self.get_event_item_data_list) \ + + encode_arr_cls(self.discovery_enemy_data_list) \ + + encode_arr_cls(self.destroy_boss_data_list) \ + + encode_arr_cls(self.mission_data_list) \ + + encode_arr_cls(self.score_data) + +class QuestSceneMultiPlayEndRequestData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.dummy_1 = decode_byte(data, off) + off += BYTE_OFF + + self.dummy_2 = decode_byte(data, off) + off += BYTE_OFF + + self.dummy_3 = decode_byte(data, off) + off += BYTE_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls) -> "QuestSceneMultiPlayEndRequestData": + ret = cls(b"\x00" * 3, 0) + ret.dummy_1 = 0 + ret.dummy_2 = 0 + ret.dummy_3 = 0 + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.dummy_1) \ + + encode_byte(self.dummy_2) \ + + encode_byte(self.dummy_3) + +class ChatSideStoryUserData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.chat_side_story_id = decode_int(data, off) + off += INT_OFF + + self.played_flag = decode_byte(data, off) + off += BYTE_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "ChatSideStoryUserData": + ret = cls(b"\x00" * 99, 0) + ret.chat_side_story_id = data['ChatSideStoryId'] + ret.played_flag = data['PlayedFlag'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.chat_side_story_id) \ + + encode_byte(self.played_flag) class PartyData(BaseHelper): - def __init__(self, data: bytes, offset: int) -> None: - sz = 0 - user_party_id = decode_str(data, offset + sz) - self.user_party_id = user_party_id[0] - sz += user_party_id[1] + def __init__(self, data: bytes, offset: int): + off = offset + self.user_party_id, new_off = decode_str(data, off) + off += new_off - self.team_no = decode_byte(data, offset + sz) - sz += BYTE_OFF - - self.party_team_data_count = decode_int(data, offset + sz) - sz += INT_OFF + self.team_no = decode_byte(data, off) + off += BYTE_OFF self.party_team_data_list: List[PartyTeamData] = [] - for _ in range(self.party_team_data_count): - tmp = PartyTeamData(data, offset + sz) - self.party_team_data_list.append(tmp) - sz += tmp.get_size() + self.party_team_data_list, new_off = decode_arr_cls(data, off, PartyTeamData) + off += new_off - self._sz = sz + self._sz = off - offset + + @classmethod + def from_args(cls, user_party_id: int, team_num: int, member1: Dict, member2: Dict, member3: Dict) -> "PartyData": + ret = cls(b"\x00" * 9, 0) + ret.user_party_id = user_party_id + ret.team_no = team_num + ret.party_team_data_list = [ + PartyTeamData.from_args(f"{user_party_id}-{team_num}-1", 1, member1), + PartyTeamData.from_args(f"{user_party_id}-{team_num}-2", 2, member2), + PartyTeamData.from_args(f"{user_party_id}-{team_num}-3", 3, member3), + ] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_str(self.user_party_id) \ + + encode_byte(self.team_no) \ + + encode_arr_cls(self.party_team_data_list) + +class SupportLogPartyData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.party_no = decode_byte(data, off) + off += BYTE_OFF + + self.arrangement_num_1_user_support_log_id, new_off = decode_str(data, off) + off += new_off + + self.arrangement_num_2_user_support_log_id, new_off = decode_str(data, off) + off += new_off + + self.arrangement_num_3_user_support_log_id, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "SupportLogPartyData": + ret = cls(b"\x00" * 99, 0) + ret.party_no = data['PartyNo'] + ret.arrangement_num_1_user_support_log_id = data['ArrangementNum1UserSupportLogId'] + ret.arrangement_num_2_user_support_log_id = data['ArrangementNum2UserSupportLogId'] + ret.arrangement_num_3_user_support_log_id = data['ArrangementNum3UserSupportLogId'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.party_no) \ + + encode_str(self.arrangement_num_1_user_support_log_id) \ + + encode_str(self.arrangement_num_2_user_support_log_id) \ + + encode_str(self.arrangement_num_3_user_support_log_id) class PlayStartRequestData(BaseHelper): def __init__(self, data: bytes, offset: int) -> None: @@ -607,7 +1994,7 @@ class ShopResourceSalesData(BaseHelper): ret += encode_str(self.discharge_user_id) ret += encode_short(self.remaining_num) ret += encode_short(self.purchase_num) - ret += encode_str(fmt_dt(self.sales_start_date)) + ret += encode_date_str(self.sales_start_date) ret += encode_arr_cls(self.sales_resource_data_list) return ret @@ -635,7 +2022,7 @@ class YuiMedalShopUserData(BaseHelper): def make(self) -> bytes: ret = encode_int(self.yui_medal_shop_id) ret += encode_int(self.purchase_num) - ret += encode_str(fmt_dt(self.last_purchase_date)) + ret += encode_date_str(self.last_purchase_date) return ret class GashaMedalShopUserData(BaseHelper): @@ -659,263 +2046,6 @@ class GashaMedalShopUserData(BaseHelper): ret += encode_int(self.purchase_num) return ret -class YuiMedalShopData(BaseHelper): - def __init__(self, data: bytes, offset: int) -> None: - super().__init__(data, offset) - self.yui_medal_shop_id = decode_int(data, offset + self._sz) - - name = decode_str(data, offset + self._sz) - self.name = name[0] - self._sz += name[1] - - description = decode_str(data, offset + self._sz) - self.description = description[0] - self._sz += description[1] - - self.selling_yui_medal = decode_short(data, offset + self._sz) - self._sz += SHORT_OFF - self.selling_col = decode_int(data, offset + self._sz) - self._sz += INT_OFF - self.selling_event_item_id = decode_int(data, offset + self._sz) - self._sz += INT_OFF - self.selling_event_item_num = decode_int(data, offset + self._sz) - self._sz += INT_OFF - self.selling_ticket_num = decode_int(data, offset + self._sz) - self._sz += INT_OFF - self.purchase_limit = decode_short(data, offset + self._sz) - self._sz += SHORT_OFF - self.pick_up_flag = decode_byte(data, offset + self._sz) - self._sz += BYTE_OFF - self.product_category = decode_byte(data, offset + self._sz) - self._sz += BYTE_OFF - self.sales_type = decode_byte(data, offset + self._sz) - self._sz += BYTE_OFF - self.target_days = decode_byte(data, offset + self._sz) - self._sz += BYTE_OFF - self.target_hour = decode_byte(data, offset + self._sz) - self._sz += BYTE_OFF - self.interval_hour = decode_byte(data, offset + self._sz) - self._sz += BYTE_OFF - - sales_start_date = decode_str(data, offset + self._sz) - self.sales_start_date = prs_dt(sales_start_date[0]) - self._sz += sales_start_date[1] - - sales_end_date = decode_str(data, offset + self._sz) - self.sales_end_date = prs_dt(sales_end_date[0]) - self._sz += sales_end_date[1] - - self.sort = decode_byte(data, offset + self._sz) - - @classmethod - def from_args(cls, shop_id: int = 0, name: str = "", desc: str = "") -> "YuiMedalShopData": - ret = cls(b"\x00" * 43, 0) - ret.yui_medal_shop_id = shop_id - ret.name = name - ret.description = desc - return ret - - def make(self) -> bytes: - ret = encode_int(self.yui_medal_shop_id) - ret += encode_str(self.name) - ret += encode_str(self.description) - ret += encode_short(self.selling_yui_medal) - ret += encode_int(self.selling_col) - ret += encode_int(self.selling_event_item_id) - ret += encode_int(self.selling_event_item_num) - ret += encode_int(self.selling_ticket_num) - ret += encode_short(self.purchase_limit) - ret += encode_byte(self.pick_up_flag) - ret += encode_byte(self.product_category) - ret += encode_byte(self.sales_type) - ret += encode_byte(self.target_days) - ret += encode_byte(self.target_hour) - ret += encode_byte(self.interval_hour) - ret += encode_str(fmt_dt(self.sales_start_date)) - ret += encode_str(fmt_dt(self.sales_end_date)) - ret += encode_byte(self.sort) - return ret - -class YuiMedalShopItemData(BaseHelper): - def __init__(self, data: bytes, offset: int) -> None: - super().__init__(data, offset) - self.yui_medal_shop_item_id = decode_int(data, offset + self._sz) - self._sz += INT_OFF - self.yui_medal_shop_id = decode_int(data, offset + self._sz) - self._sz += INT_OFF - self.common_reward_type = decode_byte(data, offset + self._sz) - self._sz += BYTE_OFF - self.common_reward_id = decode_int(data, offset + self._sz) - self._sz += INT_OFF - self.common_reward_num = decode_short(data, offset + self._sz) - self._sz += SHORT_OFF - self.strength = decode_int(data, offset + self._sz) - self._sz += INT_OFF - - self.property1_property_id = decode_int(data, offset + self._sz) - self._sz += INT_OFF - self.property1_value1 = decode_int(data, offset + self._sz) - self._sz += INT_OFF - self.property1_value2 = decode_int(data, offset + self._sz) - self._sz += INT_OFF - - self.property2_property_id = decode_int(data, offset + self._sz) - self._sz += INT_OFF - self.property2_value1 = decode_int(data, offset + self._sz) - self._sz += INT_OFF - self.property2_value2 = decode_int(data, offset + self._sz) - self._sz += INT_OFF - - self.property3_property_id = decode_int(data, offset + self._sz) - self._sz += INT_OFF - self.property3_value1 = decode_int(data, offset + self._sz) - self._sz += INT_OFF - self.property3_value2 = decode_int(data, offset + self._sz) - self._sz += INT_OFF - - self.property4_property_id = decode_int(data, offset + self._sz) - self._sz += INT_OFF - self.property4_value1 = decode_int(data, offset + self._sz) - self._sz += INT_OFF - self.property4_value2 = decode_int(data, offset + self._sz) - self._sz += INT_OFF - - @classmethod - def from_args(cls, item_id: int = 0, shop_id: int = 0, reward_type: int = 0, reward_id: int = 0, reward_num: int = 0, strength: int = 0) -> "YuiMedalShopItemData": - ret = cls(b"\x00" * 67, 0) - ret.yui_medal_shop_item_id = item_id - ret.yui_medal_shop_id = shop_id - ret.common_reward_type = reward_type - ret.common_reward_id = reward_id - ret.common_reward_num = reward_num - ret.strength = strength - return ret - - def make(self) -> bytes: - ret = encode_int(self.yui_medal_shop_item_id) - ret += encode_int(self.yui_medal_shop_id) - ret += encode_byte(self.common_reward_type) - ret += encode_int(self.common_reward_id) - ret += encode_short(self.common_reward_num) - ret += encode_int(self.strength) - - ret += encode_int(self.property1_property_id) - ret += encode_int(self.property1_value1) - ret += encode_int(self.property1_value2) - - ret += encode_int(self.property2_property_id) - ret += encode_int(self.property2_value1) - ret += encode_int(self.property2_value2) - - ret += encode_int(self.property3_property_id) - ret += encode_int(self.property3_value1) - ret += encode_int(self.property3_value2) - - ret += encode_int(self.property4_property_id) - ret += encode_int(self.property4_value1) - ret += encode_int(self.property4_value2) - return ret - -class ResEarnCampaignShop(BaseHelper): - def __init__(self, data: bytes, offset: int) -> None: - super().__init__(data, offset) - self.res_earn_campaign_shop_id = decode_int(data, offset + self._sz) - self._sz += INT_OFF - self.res_earn_campaign_application_id = decode_int(data, offset + self._sz) - self._sz += INT_OFF - - name = decode_str(data, offset + self._sz) - self.name = name[0] - self._sz += name[1] - - self.selling_yui_medal = decode_short(data, offset + self._sz) - self._sz += SHORT_OFF - self.selling_col = decode_int(data, offset + self._sz) - self._sz += INT_OFF - self.selling_event_item_id = decode_int(data, offset + self._sz) - self._sz += INT_OFF - self.selling_event_item_num = decode_int(data, offset + self._sz) - self._sz += INT_OFF - self.purchase_limit = decode_short(data, offset + self._sz) - self._sz += SHORT_OFF - self.get_application_point = decode_short(data, offset + self._sz) - self._sz += SHORT_OFF - - sales_start_date = decode_str(data, offset + self._sz) - self.sales_start_date = prs_dt(sales_start_date[0]) - self._sz += sales_start_date[1] - - sales_end_date = decode_str(data, offset + self._sz) - self.sales_end_date = prs_dt(sales_end_date[0]) - self._sz += sales_end_date[1] - - @classmethod - def from_args(cls, shop_id: int = 0, app_id: int = 0, name: str = "") -> "ResEarnCampaignShop": - ret = cls(b"\x00" * 26, 0) - ret.res_earn_campaign_shop_id = shop_id - ret.res_earn_campaign_application_id = app_id - ret.name = name - return ret - - def make(self) -> bytes: - ret = encode_int(self.res_earn_campaign_shop_id) - ret = encode_int(self.res_earn_campaign_application_id) - ret += encode_str(self.name) - ret += encode_short(self.selling_yui_medal) - ret += encode_int(self.selling_col) - ret += encode_int(self.selling_event_item_id) - ret += encode_int(self.selling_event_item_num) - ret += encode_short(self.purchase_limit) - ret += encode_short(self.get_application_point) - ret += encode_str(fmt_dt(self.sales_start_date)) - ret += encode_str(fmt_dt(self.sales_end_date)) - return ret - -class GashaMedalShop(BaseHelper): - def __init__(self, data: bytes, offset: int) -> None: - super().__init__(data, offset) - self.gasha_medal_shop_id = decode_int(data, offset + self._sz) - self._sz += INT_OFF - - name = decode_str(data, offset + self._sz) - self.name = name[0] - self._sz += name[1] - - self.gasha_medal_id = decode_int(data, offset + self._sz) - self._sz += INT_OFF - self.use_gasha_medal_num = decode_int(data, offset + self._sz) - self._sz += INT_OFF - self.purchase_limit = decode_short(data, offset + self._sz) - self._sz += SHORT_OFF - - sales_start_date = decode_str(data, offset + self._sz) - self.sales_start_date = prs_dt(sales_start_date[0]) - self._sz += sales_start_date[1] - - sales_end_date = decode_str(data, offset + self._sz) - self.sales_end_date = prs_dt(sales_end_date[0]) - self._sz += sales_end_date[1] - - @classmethod - def from_args(cls, shop_id: int = 0, name: str = "", medal_id: int = 0, medal_num: int = 0, purchase_limit: int = 0) -> "GashaMedalShop": - ret = cls(b"\x00" * 26, 0) - ret.gasha_medal_shop_id = shop_id - ret.name = name - ret.gasha_medal_id = medal_id - ret.use_gasha_medal_num = medal_num - ret.purchase_limit = purchase_limit - return ret - - def make(self) -> bytes: - ret = encode_int(self.gasha_medal_shop_id) - ret += encode_str(self.name) - ret += encode_int(self.gasha_medal_id) - ret += encode_int(self.use_gasha_medal_num) - ret += encode_short(self.purchase_limit) - ret += encode_str(fmt_dt(self.sales_start_date)) - ret += encode_str(fmt_dt(self.sales_end_date)) - return ret - class QuestHierarchyProgressDegreesRankingData(BaseHelper): def __init__(self, data: bytes, offset: int) -> None: super().__init__(data, offset) @@ -977,10 +2107,10 @@ class PopularHeroLogRankingData(BaseHelper): @classmethod def from_args(cls, ranking: int, hero_id: int, used_num: int) -> "PopularHeroLogRankingData": - ret = cls(b"\x00" * 12, 0) - cls.ranking = ranking - cls.hero_log_id = hero_id - cls.used_num = used_num + ret = cls(b"\x00" * 992, 0) + ret.rank = ranking + ret.hero_log_id = hero_id + ret.used_num = used_num return ret def make(self) -> bytes: @@ -988,3 +2118,10564 @@ class PopularHeroLogRankingData(BaseHelper): ret += encode_int(self.hero_log_id) ret += encode_int(self.used_num) return ret + +class DefragMatchBasicUserData(BaseHelper): + def __init__(self, data: bytes, offset: int) -> None: + super().__init__(data, offset) + off = offset + self.seed_flag = decode_short(data, off) + off += SHORT_OFF + + self.ad_confirm_flag = decode_byte(data, off) + off += BYTE_OFF + + self.total_league_point = decode_int(data, off) + off += INT_OFF + + self.have_league_score = decode_short(data, off) + off += SHORT_OFF + + self.class_num = decode_short(data, off) + off += SHORT_OFF + + self.hall_of_fame_confirm_flag = decode_byte(data, off) + off += BYTE_OFF + self._sz = off + + @classmethod + def from_args(cls, seed_flag: int = 0, ad_confirm_flag: int = 0, total_league_point: int = 0, league_score: int = 0, class_num: int = 1, hof_flag: int = 0) -> "DefragMatchBasicUserData": + ret = cls(b"\x00" * 12, 0) + ret.seed_flag = seed_flag + ret.ad_confirm_flag = ad_confirm_flag + ret.total_league_point = total_league_point + ret.have_league_score = league_score + ret.class_num = class_num + ret.hall_of_fame_confirm_flag = hof_flag + + def make(self) -> bytes: + ret = encode_short(self.seed_flag) + ret += encode_byte(self.ad_confirm_flag) + ret += encode_int(self.total_league_point) + ret += encode_short(self.have_league_score) + ret += encode_short(self.class_num) + ret += encode_byte(self.hall_of_fame_confirm_flag) + return ret + +class DefragMatchRankingUserData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.league_point_rank = decode_int(data, off) + off += INT_OFF + + self.league_score_rank = decode_int(data, off) + off += INT_OFF + + self.nick_name, new_off = decode_str(data, off) + off += new_off + + self.setting_title_id = decode_int(data, off) + off += INT_OFF + + self.favorite_hero_log_id = decode_int(data, off) + off += INT_OFF + + self.favorite_hero_log_awakening_stage = decode_short(data, off) + off += SHORT_OFF + + self.favorite_support_log_id = decode_int(data, off) + off += INT_OFF + + self.favorite_support_log_awakening_stage = decode_short(data, off) + off += SHORT_OFF + + self.total_league_point = decode_int(data, off) + off += INT_OFF + + self.have_league_score = decode_short(data, off) + off += SHORT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, profile: Dict) -> "DefragMatchRankingUserData": + ret = cls(b"\x00" * 34, 0) + ret.league_point_rank = 0 + ret.league_score_rank = 0 + ret.nick_name = profile['nick_name'] + ret.setting_title_id = profile['setting_title_id'] + ret.favorite_hero_log_id = profile['fav_hero'] + ret.favorite_hero_log_awakening_stage = 0 + ret.favorite_support_log_id = 0 + ret.favorite_support_log_awakening_stage = 0 + ret.total_league_point = 0 + ret.have_league_score = 0 + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.league_point_rank) \ + + encode_int(self.league_score_rank) \ + + encode_str(self.nick_name) \ + + encode_int(self.setting_title_id) \ + + encode_int(self.favorite_hero_log_id) \ + + encode_short(self.favorite_hero_log_awakening_stage) \ + + encode_int(self.favorite_support_log_id) \ + + encode_short(self.favorite_support_log_awakening_stage) \ + + encode_int(self.total_league_point) \ + + encode_short(self.have_league_score) + +class DefragMatchLeaguePointRankingData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.rank = decode_int(data, off) + off += INT_OFF + + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.store_id, new_off = decode_str(data, off) + off += new_off + + self.store_name, new_off = decode_str(data, off) + off += new_off + + self.nick_name, new_off = decode_str(data, off) + off += new_off + + self.setting_title_id = decode_int(data, off) + off += INT_OFF + + self.favorite_hero_log_id = decode_int(data, off) + off += INT_OFF + + self.favorite_hero_log_awakening_stage = decode_short(data, off) + off += SHORT_OFF + + self.favorite_support_log_id = decode_int(data, off) + off += INT_OFF + + self.favorite_support_log_awakening_stage = decode_short(data, off) + off += SHORT_OFF + + self.class_num = decode_short(data, off) + off += SHORT_OFF + + self.total_league_point = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "DefragMatchLeaguePointRankingData": + ret = cls(b"\x00" * 42, 0) + ret.rank = data['Rank'] + ret.user_id = data['UserId'] + ret.store_id = data['StoreId'] + ret.store_name = data['StoreName'] + ret.nick_name = data['NickName'] + ret.setting_title_id = data['SettingTitleId'] + ret.favorite_hero_log_id = data['FavoriteHeroLogId'] + ret.favorite_hero_log_awakening_stage = data['FavoriteHeroLogAwakeningStage'] + ret.favorite_support_log_id = data['FavoriteSupportLogId'] + ret.favorite_support_log_awakening_stage = data['FavoriteSupportLogAwakeningStage'] + ret.class_num = data['ClassNum'] + ret.total_league_point = data['TotalLeaguePoint'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.rank) \ + + encode_str(self.user_id) \ + + encode_str(self.store_id) \ + + encode_str(self.store_name) \ + + encode_str(self.nick_name) \ + + encode_int(self.setting_title_id) \ + + encode_int(self.favorite_hero_log_id) \ + + encode_short(self.favorite_hero_log_awakening_stage) \ + + encode_int(self.favorite_support_log_id) \ + + encode_short(self.favorite_support_log_awakening_stage) \ + + encode_short(self.class_num) \ + + encode_int(self.total_league_point) + +class DefragMatchLeagueScoreRankingList(BaseHelper): + def __init__(self, data: bytes, offset: int) -> None: + super().__init__(data, offset) + off = offset + self.rank = decode_byte(data, off) + off += BYTE_OFF + + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.store_id, new_off = decode_str(data, off) + off += new_off + + self.store_name, new_off = decode_str(data, off) + off += new_off + + self.nick_name, new_off = decode_str(data, off) + off += new_off + + self.setting_title_id = decode_int(data, off) + off += INT_OFF + + self.favorite_hero_log_id = decode_int(data, off) + off += INT_OFF + + self.favorite_hero_log_awakening_stage = decode_short(data, off) + off += SHORT_OFF + + self.favorite_support_log_id = decode_int(data, off) + off += INT_OFF + + self.favorite_support_log_awakening_stage = decode_short(data, off) + off += SHORT_OFF + + self.class_num = decode_short(data, off) + off += SHORT_OFF + + self.have_league_score = decode_short(data, off) + off += SHORT_OFF + + @classmethod + def from_args(cls) -> "DefragMatchLeagueScoreRankingList": + return cls(b"\x00" * 40, 0) + + def make(self) -> bytes: + ret = encode_int(self.rank) + ret += encode_str(self.user_id) + ret += encode_str(self.store_id) + ret += encode_str(self.store_name) + ret += encode_str(self.nick_name) + + ret += encode_int(self.setting_title_id) + ret += encode_int(self.favorite_hero_log_id) + ret += encode_short(self.favorite_hero_log_awakening_stage) + ret += encode_int(self.favorite_support_log_id) + ret += encode_short(self.favorite_support_log_awakening_stage) + ret += encode_short(self.class_num) + ret += encode_short(self.have_league_score) + return ret + +class AppVersionData(BaseHelper): + def __init__(self, data: bytes, offset: int) -> None: + super().__init__(data, offset) + off = offset + self.version_app_id = decode_int(data, off) + off += INT_OFF + + self.applying_start_date, new_off = decode_date_str(data, off) + off += new_off + + @classmethod + def from_args(cls, app_ver: int, start_date: datetime) -> BaseHelper: + ret = cls(b"\x00" * 8, 0) + ret.version_app_id = app_ver + ret.applying_start_date = start_date + return ret + + def make(self) -> bytes: + ret = encode_int(self.version_app_id) + ret += encode_date_str(self.applying_start_date) + return ret + +class MatchingErrorData(BaseHelper): + def __init__(self, data: bytes, offset: int) -> None: + super().__init__(data, offset) + off = 0 + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.param_1, new_off = decode_str(data, off) + off += new_off + + self.param_2, new_off = decode_str(data, off) + off += new_off + + self.param_3, new_off = decode_str(data, off) + off += new_off + + self.param_4, new_off = decode_str(data, off) + off += new_off + + self.param_5, new_off = decode_str(data, off) + off += new_off + + self.param_6, new_off = decode_str(data, off) + off += new_off + + self.param_7, new_off = decode_str(data, off) + off += new_off + + self.param_8, new_off = decode_str(data, off) + off += new_off + + self.param_9, new_off = decode_str(data, off) + off += new_off + + self.param_10, new_off = decode_str(data, off) + off += new_off + + self.error_occurred_date, new_off = decode_date_str(data, off) + off += new_off + + self._sz = off - offset + + def __str__(self) -> str: + s = f"User: {self.user_id} || " + s += f"Params: {self.param_1} {self.param_2} {self.param_3} {self.param_4} {self.param_5} {self.param_6} {self.param_7} {self.param_8} {self.param_9} {self.param_10} || " + s += f"Date: {self.error_occurred_date}" + return s + +class ReadProfileCard(BaseHelper): + def __init__(self, data: bytes, offset: int) -> None: + super().__init__(data, offset) + off = offset + self.profile_card_code, new_off = decode_str(data, off) # ID of the QR code + off += new_off + self.nick_name = decode_str(data, off) + off += new_off + + self.rank_num = decode_short(data, off) #short + off += SHORT_OFF + self.setting_title_id = decode_int(data, off) #int + off += INT_OFF + self.skill_id = decode_short(data, off) #short + off += SHORT_OFF + self.hero_log_hero_log_id = decode_int(data, off) #int + off += INT_OFF + self.hero_log_log_level = decode_short(data, off) #short + off += SHORT_OFF + self.hero_log_awakening_stage = decode_short(data, off) #short + off += SHORT_OFF + + self.hero_log_property1_property_id = decode_int(data, off) #int + off += INT_OFF + self.hero_log_property1_value1 = decode_int(data, off) #int + off += INT_OFF + self.hero_log_property1_value2 = decode_int(data, off) #int + off += INT_OFF + self.hero_log_property2_property_id = decode_int(data, off) #int + off += INT_OFF + self.hero_log_property2_value1 = decode_int(data, off) #int + off += INT_OFF + self.hero_log_property2_value2 = decode_int(data, off) #int + off += INT_OFF + self.hero_log_property3_property_id = decode_int(data, off) #int + off += INT_OFF + self.hero_log_property3_value1 = decode_int(data, off) #int + off += INT_OFF + self.hero_log_property3_value2 = decode_int(data, off) #int + off += INT_OFF + self.hero_log_property4_property_id = decode_int(data, off) #int + off += INT_OFF + self.hero_log_property4_value1 = decode_int(data, off) #int + off += INT_OFF + self.hero_log_property4_value2 = decode_int(data, off) #int + off += INT_OFF + + self.main_weapon_equipment_id = decode_int(data, off) #int + off += INT_OFF + self.main_weapon_enhancement_value = decode_short(data, off) #short + off += SHORT_OFF + self.main_weapon_awakening_stage = decode_short(data, off) #short + off += SHORT_OFF + + self.main_weapon_property1_property_id = decode_int(data, off) #int + off += INT_OFF + self.main_weapon_property1_value1 = decode_int(data, off) #int + off += INT_OFF + self.main_weapon_property1_value2 = decode_int(data, off) #int + off += INT_OFF + self.main_weapon_property2_property_id = decode_int(data, off) #int + off += INT_OFF + self.main_weapon_property2_value1 = decode_int(data, off) #int + off += INT_OFF + self.main_weapon_property2_value2 = decode_int(data, off) #int + off += INT_OFF + self.main_weapon_property3_property_id = decode_int(data, off) #int + off += INT_OFF + self.main_weapon_property3_value1 = decode_int(data, off) #int + off += INT_OFF + self.main_weapon_property3_value2 = decode_int(data, off) #int + off += INT_OFF + self.main_weapon_property4_property_id = decode_int(data, off) #int + off += INT_OFF + self.main_weapon_property4_value1 = decode_int(data, off) #int + off += INT_OFF + self.main_weapon_property4_value2 = decode_int(data, off) #int + off += INT_OFF + + self.sub_equipment_equipment_id = decode_int(data, off) #int + off += INT_OFF + self.sub_equipment_enhancement_value = decode_short(data, off) #short + off += SHORT_OFF + self.sub_equipment_awakening_stage = decode_short(data, off) #short + off += SHORT_OFF + + self.sub_equipment_property1_property_id = decode_int(data, off) #int + off += INT_OFF + self.sub_equipment_property1_value1 = decode_int(data, off) #int + off += INT_OFF + self.sub_equipment_property1_value2 = decode_int(data, off) #int + off += INT_OFF + self.sub_equipment_property2_property_id = decode_int(data, off) #int + off += INT_OFF + self.sub_equipment_property2_value1 = decode_int(data, off) #int + off += INT_OFF + self.sub_equipment_property2_value2 = decode_int(data, off) #int + off += INT_OFF + self.sub_equipment_property3_property_id = decode_int(data, off) #int + off += INT_OFF + self.sub_equipment_property3_value1 = decode_int(data, off) #int + off += INT_OFF + self.sub_equipment_property3_value2 = decode_int(data, off) #int + off += INT_OFF + self.sub_equipment_property4_property_id = decode_int(data, off) #int + off += INT_OFF + self.sub_equipment_property4_value1 = decode_int(data, off) #int + off += INT_OFF + self.sub_equipment_property4_value2 = decode_int(data, off) #int + off += INT_OFF + + self.holographic_flag = decode_byte(data, off) #byte + off += BYTE_OFF + self._sz = off - offset + + @classmethod + def from_args(cls, code: str, player_name: str) -> "ReadProfileCard": + resp = cls(b"\x00" * 44, 0) + resp.profile_card_code = code # ID of the QR code + resp.nick_name = player_name + resp.rank_num = 1 #short + resp.setting_title_id = 20005 #int + resp.skill_id = 0 #short + resp.hero_log_hero_log_id = 118000230 #int + resp.hero_log_log_level = 1 #short + resp.hero_log_awakening_stage = 1 #short + + resp.hero_log_property1_property_id = 0 #int + resp.hero_log_property1_value1 = 0 #int + resp.hero_log_property1_value2 = 0 #int + resp.hero_log_property2_property_id = 0 #int + resp.hero_log_property2_value1 = 0 #int + resp.hero_log_property2_value2 = 0 #int + resp.hero_log_property3_property_id = 0 #int + resp.hero_log_property3_value1 = 0 #int + resp.hero_log_property3_value2 = 0 #int + resp.hero_log_property4_property_id = 0 #int + resp.hero_log_property4_value1 = 0 #int + resp.hero_log_property4_value2 = 0 #int + + resp.main_weapon_equipment_id = 0 #int + resp.main_weapon_enhancement_value = 0 #short + resp.main_weapon_awakening_stage = 0 #short + + resp.main_weapon_property1_property_id = 0 #int + resp.main_weapon_property1_value1 = 0 #int + resp.main_weapon_property1_value2 = 0 #int + resp.main_weapon_property2_property_id = 0 #int + resp.main_weapon_property2_value1 = 0 #int + resp.main_weapon_property2_value2 = 0 #int + resp.main_weapon_property3_property_id = 0 #int + resp.main_weapon_property3_value1 = 0 #int + resp.main_weapon_property3_value2 = 0 #int + resp.main_weapon_property4_property_id = 0 #int + resp.main_weapon_property4_value1 = 0 #int + resp.main_weapon_property4_value2 = 0 #int + + resp.sub_equipment_equipment_id = 0 #int + resp.sub_equipment_enhancement_value = 0 #short + resp.sub_equipment_awakening_stage = 0 #short + + resp.sub_equipment_property1_property_id = 0 #int + resp.sub_equipment_property1_value1 = 0 #int + resp.sub_equipment_property1_value2 = 0 #int + resp.sub_equipment_property2_property_id = 0 #int + resp.sub_equipment_property2_value1 = 0 #int + resp.sub_equipment_property2_value2 = 0 #int + resp.sub_equipment_property3_property_id = 0 #int + resp.sub_equipment_property3_value1 = 0 #int + resp.sub_equipment_property3_value2 = 0 #int + resp.sub_equipment_property4_property_id = 0 #int + resp.sub_equipment_property4_value1 = 0 #int + resp.sub_equipment_property4_value2 = 0 #int + + resp.holographic_flag = 0 #byte + return resp + +class HeroLogUserData(BaseHelper): + def __init__(self, data: bytes, offset: int) -> None: + super().__init__(data, offset) + off = offset + self.user_hero_log_id, new_off = decode_str(data, off) + off += new_off + self.hero_log_id = decode_int(data, off) + off += INT_OFF + self.log_level = decode_short(data, off) + off += SHORT_OFF + self.max_log_level_extended_num = decode_short(data, off) + off += SHORT_OFF + self.log_exp = decode_int(data, off) + off += INT_OFF + self.possible_awakening_flag = decode_byte(data, off) + off += BYTE_OFF + self.awakening_stage = decode_short(data, off) + off += SHORT_OFF + self.awakening_exp = decode_int(data, off) + off += INT_OFF + self.skill_slot_correction_value = decode_byte(data, off) + off += BYTE_OFF + self.last_set_skill_slot1_skill_id = decode_short(data, off) + off += SHORT_OFF + self.last_set_skill_slot2_skill_id = decode_short(data, off) + off += SHORT_OFF + self.last_set_skill_slot3_skill_id = decode_short(data, off) + off += SHORT_OFF + self.last_set_skill_slot4_skill_id = decode_short(data, off) + off += SHORT_OFF + self.last_set_skill_slot5_skill_id = decode_short(data, off) + off += SHORT_OFF + self.property1_property_id = decode_int(data, off) + off += INT_OFF + self.property1_value1 = decode_int(data, off) + off += INT_OFF + self.property1_value2 = decode_int(data, off) + off += INT_OFF + self.property2_property_id = decode_int(data, off) + off += INT_OFF + self.property2_value1 = decode_int(data, off) + off += INT_OFF + self.property2_value2 = decode_int(data, off) + off += INT_OFF + self.property3_property_id = decode_int(data, off) + off += INT_OFF + self.property3_value1 = decode_int(data, off) + off += INT_OFF + self.property3_value2 = decode_int(data, off) + off += INT_OFF + self.property4_property_id = decode_int(data, off) + off += INT_OFF + self.property4_value1 = decode_int(data, off) + off += INT_OFF + self.property4_value2 = decode_int(data, off) + off += INT_OFF + self.converted_card_num = decode_short(data, off) + off += SHORT_OFF + self.shop_purchase_flag = decode_byte(data, off) + off += BYTE_OFF + self.protect_flag = decode_byte(data, off) + off += BYTE_OFF + + get_date, new_off = decode_str(data, off) + off += new_off + self.get_date = prs_dt(get_date) + self.sz = off - offset + + @classmethod + def from_args(cls, hero_data: Dict) -> "HeroLogUserData": + ret = cls(b"\x00" * 90, 0) + # Seems user_hero_log_id is a globally unique identifier, while hero_log_id identifies the hero + ret.user_hero_log_id = f"{hero_data['id']}" + ret.hero_log_id = hero_data['hero_log_id'] + + ret.log_level = hero_data['log_level'] + ret.max_log_level_extended_num = hero_data['max_level_extend_num'] + ret.log_exp = hero_data['log_exp'] + + ret.possible_awakening_flag = hero_data['is_awakenable'] + ret.awakening_stage = hero_data['awakening_stage'] + ret.awakening_exp = hero_data['awakening_exp'] + + ret.skill_slot_correction_value = 0 # Allows unlocking skill slot 4 and 5 early + ret.last_set_skill_slot1_skill_id = hero_data['skill_slot1_skill_id'] + ret.last_set_skill_slot2_skill_id = hero_data['skill_slot2_skill_id'] + ret.last_set_skill_slot3_skill_id = hero_data['skill_slot3_skill_id'] + ret.last_set_skill_slot4_skill_id = hero_data['skill_slot4_skill_id'] + ret.last_set_skill_slot5_skill_id = hero_data['skill_slot5_skill_id'] + + ret.property1_property_id = hero_data['property1_property_id'] + ret.property1_value1 = hero_data['property1_value1'] + ret.property1_value2 = hero_data['property1_value2'] + ret.property2_property_id = hero_data['property2_property_id'] + ret.property2_value1 = hero_data['property2_value1'] + ret.property2_value2 = hero_data['property2_value2'] + ret.property3_property_id = hero_data['property2_property_id'] + ret.property3_value1 = hero_data['property3_value1'] + ret.property3_value2 = hero_data['property3_value2'] + ret.property4_property_id = hero_data['property2_property_id'] + ret.property4_value1 = hero_data['property4_value1'] + ret.property4_value2 = hero_data['property4_value2'] + + ret.converted_card_num = hero_data['converted_card_num'] + ret.shop_purchase_flag = hero_data['is_shop_purchase'] + ret.protect_flag = hero_data['is_protect'] + ret.get_date = hero_data['get_date'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_str(self.user_hero_log_id) \ + + encode_int(self.hero_log_id) \ + + encode_short(self.log_level) \ + + encode_short(self.max_log_level_extended_num) \ + + encode_int(self.log_exp) \ + + encode_byte(self.possible_awakening_flag) \ + + encode_short(self.awakening_stage) \ + + encode_int(self.awakening_exp) \ + + encode_byte(self.skill_slot_correction_value) \ + + encode_short(self.last_set_skill_slot1_skill_id) \ + + encode_short(self.last_set_skill_slot2_skill_id) \ + + encode_short(self.last_set_skill_slot3_skill_id) \ + + encode_short(self.last_set_skill_slot4_skill_id) \ + + encode_short(self.last_set_skill_slot5_skill_id) \ + + encode_int(self.property1_property_id) \ + + encode_int(self.property1_value1) \ + + encode_int(self.property1_value2) \ + + encode_int(self.property2_property_id) \ + + encode_int(self.property2_value1) \ + + encode_int(self.property2_value2) \ + + encode_int(self.property3_property_id) \ + + encode_int(self.property3_value1) \ + + encode_int(self.property3_value2) \ + + encode_int(self.property4_property_id) \ + + encode_int(self.property4_value1) \ + + encode_int(self.property4_value2) \ + + encode_short(self.converted_card_num) \ + + encode_byte(self.shop_purchase_flag) \ + + encode_byte(self.protect_flag) \ + + encode_date_str(self.get_date) + +class YuiMedalBonusUserData(BaseHelper): + def __init__(self, data: bytes, offset: int) -> None: + super().__init__(data, offset) + off = offset + self.elapsed_days = decode_int(data, off) + off += INT_OFF + self.loop_num = decode_int(data, off) + off += INT_OFF + + last_check_date, new_off = decode_str(data, off) + off += new_off + self.last_check_date = prs_dt(last_check_date) + + last_get_date, new_off = decode_str(data, off) + off += new_off + self.last_get_date = prs_dt(last_get_date) + + self._sz = off - offset + + @classmethod + def from_args(cls, elapsed_days: int = 0, loop_num: int = 0) -> BaseHelper: + ret = cls(b"\x00" * 996, 0) + ret.elapsed_days = elapsed_days + ret.loop_num = loop_num + ret.last_check_date = datetime.fromtimestamp(0) + ret.last_get_date = datetime.fromtimestamp(0) + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.elapsed_days) \ + + encode_int(self.loop_num) \ + + encode_date_str(self.last_check_date) \ + + encode_date_str(self.last_get_date) + +class UserBasicData(BaseHelper): + def __init__(self, data: bytes, offset: int) -> None: + super().__init__(data, offset) + off = offset + self.user_type = decode_short(data, off) + off += SHORT_OFF + + self.nick_name, new_off = decode_str(data, off) + off += new_off + + self.rank_num = decode_short(data, off) + off += SHORT_OFF + + self.rank_exp = decode_int(data, off) + off += INT_OFF + + self.own_col = decode_int(data, off) + off += INT_OFF + + self.own_vp = decode_int(data, off) + off += INT_OFF + + self.own_yui_medal = decode_int(data, off) + off += INT_OFF + + self.setting_title_id = decode_int(data, off) + off += INT_OFF + + self.favorite_user_hero_log_id, new_off = decode_str(data, off) + off += new_off + + self.favorite_user_support_log_id, new_off = decode_str(data, off) + off += new_off + + self.my_store_id, new_off = decode_str(data, off) + off += new_off + + self.my_store_name, new_off = decode_str(data, off) + off += new_off + + user_reg_date, new_off = decode_str(data, off) + self.user_reg_date = prs_dt(user_reg_date) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, profile_data: Dict, shop_name: str = "ARTEMiS") -> "UserBasicData": + ret = cls(b"\0" * 52, 0) + ret.user_type = profile_data['user_type'] + ret.nick_name = profile_data['nick_name'] + ret.rank_num = profile_data['rank_num'] + ret.rank_exp = profile_data['rank_exp'] + ret.own_col = profile_data['own_col'] + ret.own_vp = profile_data['own_vp'] + ret.own_yui_medal = profile_data['own_yui_medal'] + ret.setting_title_id = profile_data['setting_title_id'] + ret.favorite_user_hero_log_id = profile_data['fav_hero'] + ret.favorite_user_support_log_id = "" # TODO: Supports + ret.my_store_id = "JPN0" + f"{profile_data['my_shop']:04X}" if profile_data["my_shop"] else "0" + ret.my_store_name = shop_name + ret.user_reg_date = profile_data['when_register'] if profile_data['when_register'] else prs_dt("20230101120000") + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.user_type) \ + + encode_str(self.nick_name) \ + + encode_short(self.rank_num) \ + + encode_int(self.rank_exp) \ + + encode_int(self.own_col) \ + + encode_int(self.own_vp) \ + + encode_int(self.own_yui_medal) \ + + encode_int(self.setting_title_id) \ + + encode_str(self.favorite_user_hero_log_id) \ + + encode_str(self.favorite_user_support_log_id) \ + + encode_str(self.my_store_id) \ + + encode_str(self.my_store_name) \ + + encode_date_str(self.user_reg_date) \ + +class VpGashaTicketData(BaseHelper): + def __init__(self, data: bytes, offset: int) -> None: + super().__init__(data, offset) + off = offset + self.remaining_own_vp_gasha_ticket = decode_int(data, off) + off += INT_OFF + + self.expire_date, new_off = decode_date_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, ticket_remain: int, exp_date: datetime) -> "VpGashaTicketData": + ret = cls(b"\x00" * 8, 0) + ret.remaining_own_vp_gasha_ticket = ticket_remain + ret.expire_date = exp_date + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.remaining_own_vp_gasha_ticket) \ + + encode_date_str(self.expire_date) + +class BeginnerMissionUserData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.ad_confirm_flag = decode_byte(data, off) + off += BYTE_OFF + + self.ad_confirm_date, new_off = decode_date_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, confirm_date: datetime, confirm_flg: bool = False) -> "BeginnerMissionUserData": + ret = cls(b"\x00" * 99, 0) + ret.ad_confirm_flag = confirm_flg + ret.ad_confirm_date = confirm_date + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.ad_confirm_flag) \ + + encode_date_str(self.ad_confirm_date) + +class EquipmentUserData(BaseHelper): + def __init__(self, data: bytes, offset: int) -> None: + super().__init__(data, offset) + off = offset + self.user_equipment_id, new_off = decode_str(data, off) + off += new_off + + self.equipment_id = decode_int(data, off) + off += INT_OFF + self.enhancement_value = decode_short(data, off) + off += SHORT_OFF + self.max_enhancement_value_extended_num = decode_short(data, off) + off += SHORT_OFF + self.enhancement_exp = decode_int(data, off) + off += INT_OFF + self.possible_awakening_flag = decode_byte(data, off) + off += BYTE_OFF + self.awakening_stage = decode_short(data, off) + off += SHORT_OFF + self.awakening_exp = decode_int(data, off) + off += INT_OFF + + self.property1_property_id = decode_int(data, off) + off += INT_OFF + self.property1_value1 = decode_int(data, off) + off += INT_OFF + self.property1_value2 = decode_int(data, off) + off += INT_OFF + self.property2_property_id = decode_int(data, off) + off += INT_OFF + self.property2_value1 = decode_int(data, off) + off += INT_OFF + self.property2_value2 = decode_int(data, off) + off += INT_OFF + self.property3_property_id = decode_int(data, off) + off += INT_OFF + self.property3_value1 = decode_int(data, off) + off += INT_OFF + self.property3_value2 = decode_int(data, off) + off += INT_OFF + self.property4_property_id = decode_int(data, off) + off += INT_OFF + self.property4_value1 = decode_int(data, off) + off += INT_OFF + self.property4_value2 = decode_int(data, off) + off += INT_OFF + + self.converted_card_num = decode_short(data, off) + off += SHORT_OFF + self.shop_purchase_flag = decode_byte(data, off) + off += BYTE_OFF + self.protect_flag = decode_byte(data, off) + off += BYTE_OFF + + self.get_date, new_off = decode_date_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, equip_data: Dict) -> "EquipmentUserData": + ret = cls(b"\x00" * 79, 0) + ret.user_equipment_id = str(equip_data['id']) + ret.equipment_id = equip_data['equipment_id'] + ret.enhancement_value = equip_data['enhancement_value'] + ret.max_enhancement_value_extended_num = 0 #equip_data['max_enhancement_value_extended_num'] TODO: This + ret.enhancement_exp = equip_data['enhancement_exp'] + ret.possible_awakening_flag = equip_data['possible_awakening_flag'] + ret.awakening_stage = equip_data['awakening_stage'] + ret.awakening_exp = equip_data['awakening_exp'] + ret.property1_property_id = equip_data['property1_property_id'] + ret.property1_value1 = equip_data['property1_value1'] + ret.property1_value2 = equip_data['property1_value2'] + ret.property2_property_id = equip_data['property2_property_id'] + ret.property2_value1 = equip_data['property2_value1'] + ret.property2_value2 = equip_data['property2_value2'] + ret.property3_property_id = equip_data['property2_property_id'] + ret.property3_value1 = equip_data['property3_value1'] + ret.property3_value2 = equip_data['property3_value2'] + ret.property4_property_id = equip_data['property2_property_id'] + ret.property4_value1 = equip_data['property4_value1'] + ret.property4_value2 = equip_data['property4_value2'] + ret.converted_card_num = equip_data['converted_card_num'] + ret.shop_purchase_flag = equip_data['is_shop_purchase'] + ret.protect_flag = equip_data['is_protect'] + ret.get_date = equip_data['get_date'] + return ret + + def make(self) -> bytes: + return encode_str(self.user_equipment_id) \ + + encode_int(self.equipment_id) \ + + encode_short(self.enhancement_value) \ + + encode_short(self.max_enhancement_value_extended_num) \ + + encode_int(self.enhancement_exp) \ + + encode_byte(self.possible_awakening_flag) \ + + encode_short(self.awakening_stage) \ + + encode_int(self.awakening_exp) \ + + encode_int(self.property1_property_id) \ + + encode_int(self.property1_value1) \ + + encode_int(self.property1_value2) \ + + encode_int(self.property2_property_id) \ + + encode_int(self.property2_value1) \ + + encode_int(self.property2_value2) \ + + encode_int(self.property3_property_id) \ + + encode_int(self.property3_value1) \ + + encode_int(self.property3_value2) \ + + encode_int(self.property4_property_id) \ + + encode_int(self.property4_value1) \ + + encode_int(self.property4_value2) \ + + encode_short(self.converted_card_num) \ + + encode_byte(self.shop_purchase_flag) \ + + encode_byte(self.protect_flag) \ + + encode_date_str(self.get_date) + +class ItemUserData(BaseHelper): + def __init__(self, data: bytes, offset: int) -> None: + super().__init__(data, offset) + off = offset + self.user_item_id, new_off = decode_str(data, off) + off += new_off + self.item_id = decode_int(data, off) + off += INT_OFF + self.protect_flag = decode_byte(data, off) + off += BYTE_OFF + self.get_date = decode_date_str(data, off) + + self._sz = off - offset + + @classmethod + def from_args(cls, item_data: Dict) -> BaseHelper: + ret = cls(b"\x00" * 993, 0) + ret.user_item_id = str(item_data['id']) + ret.item_id = item_data['item_id'] + ret.protect_flag = 0 + ret.get_date = item_data['get_date'] + return ret + + def make(self) -> bytes: + return encode_str(self.user_item_id) \ + + encode_int(self.item_id) \ + + encode_byte(self.protect_flag) \ + + encode_date_str(self.get_date) + +class SupportLogUserData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.user_support_log_id, new_off = decode_str(data, off) + off += new_off + + self.support_log_id = decode_int(data, off) + off += INT_OFF + + self.possible_awakening_flag = decode_byte(data, off) + off += BYTE_OFF + + self.awakening_stage = decode_short(data, off) + off += SHORT_OFF + + self.awakening_exp = decode_int(data, off) + off += INT_OFF + + self.converted_card_num = decode_short(data, off) + off += SHORT_OFF + + self.shop_purchase_flag = decode_byte(data, off) + off += BYTE_OFF + + self.protect_flag = decode_byte(data, off) + off += BYTE_OFF + + self.get_date, new_off = decode_date_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, user_support_log: str, support_log: int) -> "SupportLogUserData": + ret = cls(b"\x00" * 23, 0) + ret.user_support_log_id = user_support_log + ret.support_log_id = support_log + ret.possible_awakening_flag = 0 + ret.awakening_stage = 0 + ret.awakening_exp = 0 + ret.converted_card_num = 0 + ret.shop_purchase_flag = 0 + ret.protect_flag = 0 + ret.get_date = prs_dt() + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_str(self.user_support_log_id) \ + + encode_int(self.support_log_id) \ + + encode_byte(self.possible_awakening_flag) \ + + encode_short(self.awakening_stage) \ + + encode_int(self.awakening_exp) \ + + encode_short(self.converted_card_num) \ + + encode_byte(self.shop_purchase_flag) \ + + encode_byte(self.protect_flag) \ + + encode_date_str(self.get_date) + +class EventItemUserData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.user_event_item_id, new_off = decode_str(data, off) + off += new_off + + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.event_item_id = decode_int(data, off) + off += INT_OFF + + self.own_num = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "EventItemUserData": + ret = cls(b"\x00" * 996, 0) + ret.user_event_item_id = data['UserEventItemId'] + ret.user_id = data['UserId'] + ret.event_item_id = data['EventItemId'] + ret.own_num = data['OwnNum'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_str(self.user_event_item_id) \ + + encode_str(self.user_id) \ + + encode_int(self.event_item_id) \ + + encode_int(self.own_num) + +class GashaMedalUserData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.user_gasha_medal_id, new_off = decode_str(data, off) + off += new_off + + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.gasha_medal_id = decode_int(data, off) + off += INT_OFF + + self.own_num = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "GashaMedalUserData": + ret = cls(b"\x00" * 996, 0) + ret.user_gasha_medal_id = data['UserGashaMedalId'] + ret.user_id = data['UserId'] + ret.gasha_medal_id = data['GashaMedalId'] + ret.own_num = data['OwnNum'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_str(self.user_gasha_medal_id) \ + + encode_str(self.user_id) \ + + encode_int(self.gasha_medal_id) \ + + encode_int(self.own_num) + +class TitleUserData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.user_title_id, new_off = decode_str(data, off) + off += new_off + + self.title_id = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, user_title_id: str, title_id: int) -> "TitleUserData": + ret = cls(b"\x00" * 8, 0) + ret.user_title_id = user_title_id + ret.title_id = title_id + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_str(self.user_title_id) \ + + encode_int(self.title_id) + +class PlayerRankData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.player_rank_id = decode_short(data, off) + off += SHORT_OFF + + self.total_exp = decode_int(data, off) + off += INT_OFF + + self.storage = decode_short(data, off) + off += SHORT_OFF + + self.team_preset = decode_short(data, off) + off += SHORT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "PlayerRankData": + ret = cls(b"\x00" * 99, 0) + ret.player_rank_id = data['PlayerRankId'] + ret.total_exp = data['TotalExp'] + ret.storage = data['Storage'] + ret.team_preset = data['TeamPreset'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.player_rank_id) \ + + encode_int(self.total_exp) \ + + encode_short(self.storage) \ + + encode_short(self.team_preset) + +class TitleData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.title_id = decode_int(data, off) + off += INT_OFF + + self.display_name, new_off = decode_str(data, off) + off += new_off + + self.requirement = decode_int(data, off) + off += INT_OFF + + self.value1 = decode_int(data, off) + off += INT_OFF + + self.value2 = decode_int(data, off) + off += INT_OFF + + self.rank = decode_int(data, off) + off += INT_OFF + + self.image_file_path, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "TitleData": + ret = cls(b"\x00" * 99, 0) + ret.title_id = data['TitleId'] + ret.display_name = data['DisplayName'] + ret.requirement = data['Requirement'] + ret.value1 = data['Value1'] + ret.value2 = data['Value2'] + ret.rank = data['Rank'] + ret.image_file_path = data['ImageFilePath'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.title_id) \ + + encode_str(self.display_name) \ + + encode_int(self.requirement) \ + + encode_int(self.value1) \ + + encode_int(self.value2) \ + + encode_int(self.rank) \ + + encode_str(self.image_file_path) + +class FragmentData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.fragment_id = decode_short(data, off) + off += SHORT_OFF + + self.exp = decode_int(data, off) + off += INT_OFF + + self.comment_id, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "FragmentData": + ret = cls(b"\x00" * 99, 0) + ret.fragment_id = data['FragmentId'] + ret.exp = data['Exp'] + ret.comment_id = data['CommentId'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.fragment_id) \ + + encode_int(self.exp) \ + + encode_str(self.comment_id) + +class RewardTableData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.reward_table_id = decode_int(data, off) + off += INT_OFF + + self.reward_table_sub_id = decode_int(data, off) + off += INT_OFF + + self.unanalyzed_log_grade_id = decode_int(data, off) + off += INT_OFF + + self.common_reward_type = decode_int(data, off) + off += INT_OFF + + self.common_reward_id = decode_int(data, off) + off += INT_OFF + + self.common_reward_num = decode_int(data, off) + off += INT_OFF + + self.strength_min = decode_int(data, off) + off += INT_OFF + + self.strength_max = decode_int(data, off) + off += INT_OFF + + self.property_table_sub_id = decode_int(data, off) + off += INT_OFF + + self.rate = decode_int(data, off) + off += INT_OFF + + self.quest_info_display_flag = decode_byte(data, off) + off += BYTE_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "RewardTableData": + ret = cls(b"\x00" * 99, 0) + ret.reward_table_id = data['RewardTableId'] + ret.reward_table_sub_id = data['RewardTableSubId'] + ret.unanalyzed_log_grade_id = data['UnanalyzedLogGradeId'] + ret.common_reward_type = data['CommonRewardType'] + ret.common_reward_id = data['CommonRewardId'] + ret.common_reward_num = data['CommonRewardNum'] + ret.strength_min = data['StrengthMin'] + ret.strength_max = data['StrengthMax'] + ret.property_table_sub_id = data['PropertyTableSubId'] + ret.rate = data['Rate'] + ret.quest_info_display_flag = data['QuestInfoDisplayFlag'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.reward_table_id) \ + + encode_int(self.reward_table_sub_id) \ + + encode_int(self.unanalyzed_log_grade_id) \ + + encode_int(self.common_reward_type) \ + + encode_int(self.common_reward_id) \ + + encode_int(self.common_reward_num) \ + + encode_int(self.strength_min) \ + + encode_int(self.strength_max) \ + + encode_int(self.property_table_sub_id) \ + + encode_int(self.rate) \ + + encode_byte(self.quest_info_display_flag) + +class RewardSetData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.reward_set_id = decode_int(data, off) + off += INT_OFF + + self.reward_set_sub_id = decode_int(data, off) + off += INT_OFF + + self.common_reward_type = decode_int(data, off) + off += INT_OFF + + self.common_reward_id = decode_int(data, off) + off += INT_OFF + + self.common_reward_num = decode_int(data, off) + off += INT_OFF + + self.strength = decode_int(data, off) + off += INT_OFF + + self.property1_property_id = decode_int(data, off) + off += INT_OFF + + self.property1_value1 = decode_int(data, off) + off += INT_OFF + + self.property1_value2 = decode_int(data, off) + off += INT_OFF + + self.property2_property_id = decode_int(data, off) + off += INT_OFF + + self.property2_value1 = decode_int(data, off) + off += INT_OFF + + self.property2_value2 = decode_int(data, off) + off += INT_OFF + + self.property3_property_id = decode_int(data, off) + off += INT_OFF + + self.property3_value1 = decode_int(data, off) + off += INT_OFF + + self.property3_value2 = decode_int(data, off) + off += INT_OFF + + self.property4_property_id = decode_int(data, off) + off += INT_OFF + + self.property4_value1 = decode_int(data, off) + off += INT_OFF + + self.property4_value2 = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "RewardSetData": + ret = cls(b"\x00" * 99, 0) + ret.reward_set_id = data['RewardSetId'] + ret.reward_set_sub_id = data['RewardSetSubId'] + ret.common_reward_type = data['CommonRewardType'] + ret.common_reward_id = data['CommonRewardId'] + ret.common_reward_num = data['CommonRewardNum'] + ret.strength = data['Strength'] + ret.property1_property_id = data['Property1PropertyId'] + ret.property1_value1 = data['Property1Value1'] + ret.property1_value2 = data['Property1Value2'] + ret.property2_property_id = data['Property2PropertyId'] + ret.property2_value1 = data['Property2Value1'] + ret.property2_value2 = data['Property2Value2'] + ret.property3_property_id = data['Property3PropertyId'] + ret.property3_value1 = data['Property3Value1'] + ret.property3_value2 = data['Property3Value2'] + ret.property4_property_id = data['Property4PropertyId'] + ret.property4_value1 = data['Property4Value1'] + ret.property4_value2 = data['Property4Value2'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.reward_set_id) \ + + encode_int(self.reward_set_sub_id) \ + + encode_int(self.common_reward_type) \ + + encode_int(self.common_reward_id) \ + + encode_int(self.common_reward_num) \ + + encode_int(self.strength) \ + + encode_int(self.property1_property_id) \ + + encode_int(self.property1_value1) \ + + encode_int(self.property1_value2) \ + + encode_int(self.property2_property_id) \ + + encode_int(self.property2_value1) \ + + encode_int(self.property2_value2) \ + + encode_int(self.property3_property_id) \ + + encode_int(self.property3_value1) \ + + encode_int(self.property3_value2) \ + + encode_int(self.property4_property_id) \ + + encode_int(self.property4_value1) \ + + encode_int(self.property4_value2) + +class UnanalyzedLogGradeData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.unanalyzed_log_grade_id = decode_int(data, off) + off += INT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self.comment_id, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "UnanalyzedLogGradeData": + ret = cls(b"\x00" * 99, 0) + ret.unanalyzed_log_grade_id = data['UnanalyzedLogGradeId'] + ret.name = data['Name'] + ret.comment_id = data['CommentId'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.unanalyzed_log_grade_id) \ + + encode_str(self.name) \ + + encode_str(self.comment_id) + +class AppointLeaderParamData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.appoint_leader_param_id = decode_short(data, off) + off += SHORT_OFF + + self.initial_synchro_rate = decode_int(data, off) + off += INT_OFF + + self.appoint_leader_increment_synchro_rate = decode_int(data, off) + off += INT_OFF + + self.awakening_increment_synchro_rate = decode_int(data, off) + off += INT_OFF + + self.foil_add_synchro_rate = decode_int(data, off) + off += INT_OFF + + self.appoint_leader_trust_bonus = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "AppointLeaderParamData": + ret = cls(b"\x00" * 99, 0) + ret.appoint_leader_param_id = data['AppointLeaderParamId'] + ret.initial_synchro_rate = data['InitialSynchroRate'] + ret.appoint_leader_increment_synchro_rate = data['AppointLeaderIncrementSynchroRate'] + ret.awakening_increment_synchro_rate = data['AwakeningIncrementSynchroRate'] + ret.foil_add_synchro_rate = data['FoilAddSynchroRate'] + ret.appoint_leader_trust_bonus = data['AppointLeaderTrustBonus'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.appoint_leader_param_id) \ + + encode_int(self.initial_synchro_rate) \ + + encode_int(self.appoint_leader_increment_synchro_rate) \ + + encode_int(self.awakening_increment_synchro_rate) \ + + encode_int(self.foil_add_synchro_rate) \ + + encode_int(self.appoint_leader_trust_bonus) + +class AppointLeaderEffectData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.appoint_leader_effect_id = decode_short(data, off) + off += SHORT_OFF + + self.chara_id = decode_short(data, off) + off += SHORT_OFF + + self.info_text_format, new_off = decode_str(data, off) + off += new_off + + self.appoint_leader_effect_type_id = decode_short(data, off) + off += SHORT_OFF + + self.low_effect_value, new_off = decode_str(data, off) + off += new_off + + self.middle_effect_value, new_off = decode_str(data, off) + off += new_off + + self.high_effect_value, new_off = decode_str(data, off) + off += new_off + + self.max_effect_value, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "AppointLeaderEffectData": + ret = cls(b"\x00" * 99, 0) + ret.appoint_leader_effect_id = data['AppointLeaderEffectId'] + ret.chara_id = data['CharaId'] + ret.info_text_format = data['InfoTextFormat'] + ret.appoint_leader_effect_type_id = data['AppointLeaderEffectTypeId'] + ret.low_effect_value = data['LowEffectValue'] + ret.middle_effect_value = data['MiddleEffectValue'] + ret.high_effect_value = data['HighEffectValue'] + ret.max_effect_value = data['MaxEffectValue'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.appoint_leader_effect_id) \ + + encode_short(self.chara_id) \ + + encode_str(self.info_text_format) \ + + encode_short(self.appoint_leader_effect_type_id) \ + + encode_str(self.low_effect_value) \ + + encode_str(self.middle_effect_value) \ + + encode_str(self.high_effect_value) \ + + encode_str(self.max_effect_value) + +class AppointLeaderEffectTypeData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.appoint_leader_effect_type_id = decode_short(data, off) + off += SHORT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "AppointLeaderEffectTypeData": + ret = cls(b"\x00" * 99, 0) + ret.appoint_leader_effect_type_id = data['AppointLeaderEffectTypeId'] + ret.name = data['Name'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.appoint_leader_effect_type_id) \ + + encode_str(self.name) + +class RarityData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.rarity_id = decode_short(data, off) + off += SHORT_OFF + + self.require_col_my_card = decode_int(data, off) + off += INT_OFF + + self.require_col_other_card = decode_int(data, off) + off += INT_OFF + + self.require_medal_other_card = decode_int(data, off) + off += INT_OFF + + self.synthesis_exp_rate, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "RarityData": + ret = cls(b"\x00" * 99, 0) + ret.rarity_id = data['RarityId'] + ret.require_col_my_card = data['RequireColMyCard'] + ret.require_col_other_card = data['RequireColOtherCard'] + ret.require_medal_other_card = data['RequireMedalOtherCard'] + ret.synthesis_exp_rate = data['SynthesisExpRate'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.rarity_id) \ + + encode_int(self.require_col_my_card) \ + + encode_int(self.require_col_other_card) \ + + encode_int(self.require_medal_other_card) \ + + encode_str(self.synthesis_exp_rate) + +class CompositionEventData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.composition_event_id = decode_short(data, off) + off += SHORT_OFF + + self.composition_exp_rate, new_off = decode_str(data, off) + off += new_off + + self.awakening_exp_rate, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "CompositionEventData": + ret = cls(b"\x00" * 99, 0) + ret.composition_event_id = data['CompositionEventId'] + ret.composition_exp_rate = data['CompositionExpRate'] + ret.awakening_exp_rate = data['AwakeningExpRate'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.composition_event_id) \ + + encode_str(self.composition_exp_rate) \ + + encode_str(self.awakening_exp_rate) + +class CompositionParamData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.composition_param_id = decode_short(data, off) + off += SHORT_OFF + + self.use_value = decode_int(data, off) + off += INT_OFF + + self.max_extended_use_coef, new_off = decode_str(data, off) + off += new_off + + self.awakening_coef, new_off = decode_str(data, off) + off += new_off + + self.use_value_support_log = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "CompositionParamData": + ret = cls(b"\x00" * 99, 0) + ret.composition_param_id = data['CompositionParamId'] + ret.use_value = data['UseValue'] + ret.max_extended_use_coef = data['MaxExtendedUseCoef'] + ret.awakening_coef = data['AwakeningCoef'] + ret.use_value_support_log = data['UseValueSupportLog'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.composition_param_id) \ + + encode_int(self.use_value) \ + + encode_str(self.max_extended_use_coef) \ + + encode_str(self.awakening_coef) \ + + encode_int(self.use_value_support_log) + +class GamePlayPriceData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.game_play_price_id = decode_short(data, off) + off += SHORT_OFF + + self.player_rank_id = decode_short(data, off) + off += SHORT_OFF + + self.episode_ticket = decode_int(data, off) + off += INT_OFF + + self.trial_tower_ticket = decode_short(data, off) + off += SHORT_OFF + + self.custom_retry_ticket = decode_short(data, off) + off += SHORT_OFF + + self.subdue_ticket = decode_short(data, off) + off += SHORT_OFF + + self.continue_ticket = decode_short(data, off) + off += SHORT_OFF + + self.continue_credit = decode_short(data, off) + off += SHORT_OFF + + self.extend_time_ticket = decode_short(data, off) + off += SHORT_OFF + + self.reward_grade_up_ticket = decode_short(data, off) + off += SHORT_OFF + + self.reward_grade_up_credit = decode_short(data, off) + off += SHORT_OFF + + self.give_free_ticket = decode_short(data, off) + off += SHORT_OFF + + self.free_continue_num = decode_short(data, off) + off += SHORT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "GamePlayPriceData": + ret = cls(b"\x00" * 99, 0) + ret.game_play_price_id = data['GamePlayPriceId'] + ret.player_rank_id = data['PlayerRankId'] + ret.episode_ticket = data['EpisodeTicket'] + ret.trial_tower_ticket = data['TrialTowerTicket'] + ret.custom_retry_ticket = data['CustomRetryTicket'] + ret.subdue_ticket = data['SubdueTicket'] + ret.continue_ticket = data['ContinueTicket'] + ret.continue_credit = data['ContinueCredit'] + ret.extend_time_ticket = data['ExtendTimeTicket'] + ret.reward_grade_up_ticket = data['RewardGradeUpTicket'] + ret.reward_grade_up_credit = data['RewardGradeUpCredit'] + ret.give_free_ticket = data['GiveFreeTicket'] + ret.free_continue_num = data['FreeContinueNum'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.game_play_price_id) \ + + encode_short(self.player_rank_id) \ + + encode_int(self.episode_ticket) \ + + encode_short(self.trial_tower_ticket) \ + + encode_short(self.custom_retry_ticket) \ + + encode_short(self.subdue_ticket) \ + + encode_short(self.continue_ticket) \ + + encode_short(self.continue_credit) \ + + encode_short(self.extend_time_ticket) \ + + encode_short(self.reward_grade_up_ticket) \ + + encode_short(self.reward_grade_up_credit) \ + + encode_short(self.give_free_ticket) \ + + encode_short(self.free_continue_num) + +class BuyTicketData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.buy_ticket_id = decode_int(data, off) + off += INT_OFF + + self.buy_ticket_pattern = decode_int(data, off) + off += INT_OFF + + self.credit_cnt = decode_int(data, off) + off += INT_OFF + + self.get_ticket_cnt = decode_int(data, off) + off += INT_OFF + + self.get_bonus_vp = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "BuyTicketData": + ret = cls(b"\x00" * 99, 0) + ret.buy_ticket_id = data['BuyTicketId'] + ret.buy_ticket_pattern = data['BuyTicketPattern'] + ret.credit_cnt = data['CreditCnt'] + ret.get_ticket_cnt = data['GetTicketCnt'] + ret.get_bonus_vp = data['GetBonusVp'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.buy_ticket_id) \ + + encode_int(self.buy_ticket_pattern) \ + + encode_int(self.credit_cnt) \ + + encode_int(self.get_ticket_cnt) \ + + encode_int(self.get_bonus_vp) + +class TipsData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.tips_id = decode_int(data, off) + off += INT_OFF + + self.tips_category = decode_byte(data, off) + off += BYTE_OFF + + self.tips_text, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "TipsData": + ret = cls(b"\x00" * 99, 0) + ret.tips_id = data['TipsId'] + ret.tips_category = data['TipsCategory'] + ret.tips_text = data['TipsText'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.tips_id) \ + + encode_byte(self.tips_category) \ + + encode_str(self.tips_text) + +class CapData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.cap_id = decode_short(data, off) + off += SHORT_OFF + + self.trust1_cap = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "CapData": + ret = cls(b"\x00" * 99, 0) + ret.cap_id = data['CapId'] + ret.trust1_cap = data['Trust1Cap'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.cap_id) \ + + encode_int(self.trust1_cap) + +class HeroLogData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.hero_log_id = decode_int(data, off) + off += INT_OFF + + self.chara_id = decode_short(data, off) + off += SHORT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self.nickname, new_off = decode_str(data, off) + off += new_off + + self.rarity = decode_byte(data, off) + off += BYTE_OFF + + self.weapon_type_id = decode_short(data, off) + off += SHORT_OFF + + self.hero_log_role_id = decode_short(data, off) + off += SHORT_OFF + + self.costume_type_id = decode_short(data, off) + off += SHORT_OFF + + self.unit_id = decode_int(data, off) + off += INT_OFF + + self.default_equipment_id1 = decode_int(data, off) + off += INT_OFF + + self.default_equipment_id2 = decode_int(data, off) + off += INT_OFF + + self.skill_table_sub_id = decode_int(data, off) + off += INT_OFF + + self.hp_min = decode_int(data, off) + off += INT_OFF + + self.hp_max = decode_int(data, off) + off += INT_OFF + + self.str_min = decode_int(data, off) + off += INT_OFF + + self.str_max = decode_int(data, off) + off += INT_OFF + + self.vit_min = decode_int(data, off) + off += INT_OFF + + self.vit_max = decode_int(data, off) + off += INT_OFF + + self.int_min = decode_int(data, off) + off += INT_OFF + + self.int_max = decode_int(data, off) + off += INT_OFF + + self.property1_property_id = decode_int(data, off) + off += INT_OFF + + self.property1_value1 = decode_int(data, off) + off += INT_OFF + + self.property1_value2 = decode_int(data, off) + off += INT_OFF + + self.property2_property_id = decode_int(data, off) + off += INT_OFF + + self.property2_value1 = decode_int(data, off) + off += INT_OFF + + self.property2_value2 = decode_int(data, off) + off += INT_OFF + + self.property3_property_id = decode_int(data, off) + off += INT_OFF + + self.property3_value1 = decode_int(data, off) + off += INT_OFF + + self.property3_value2 = decode_int(data, off) + off += INT_OFF + + self.property4_property_id = decode_int(data, off) + off += INT_OFF + + self.property4_value1 = decode_int(data, off) + off += INT_OFF + + self.property4_value2 = decode_int(data, off) + off += INT_OFF + + self.flavor_text, new_off = decode_str(data, off) + off += new_off + + self.sale_price = decode_int(data, off) + off += INT_OFF + + self.composition_exp = decode_int(data, off) + off += INT_OFF + + self.awakening_exp = decode_int(data, off) + off += INT_OFF + + self.slot4_unlock_level = decode_int(data, off) + off += INT_OFF + + self.slot5_unlock_level = decode_int(data, off) + off += INT_OFF + + self.cut_in_image, new_off = decode_str(data, off) + off += new_off + + self.cut_in_image_awake, new_off = decode_str(data, off) + off += new_off + + self.cut_in_upper_side_text = decode_byte(data, off) + off += BYTE_OFF + + self.chara_comment_image, new_off = decode_str(data, off) + off += new_off + + self.chara_comment_image_awake, new_off = decode_str(data, off) + off += new_off + + self.quest_start_introduce, new_off = decode_str(data, off) + off += new_off + + self.quest_start_introduce_awake, new_off = decode_str(data, off) + off += new_off + + self.quest_chara_icon, new_off = decode_str(data, off) + off += new_off + + self.quest_chara_icon_awake, new_off = decode_str(data, off) + off += new_off + + self.quest_chara_icon_loss, new_off = decode_str(data, off) + off += new_off + + self.quest_chara_icon_awake_loss, new_off = decode_str(data, off) + off += new_off + + self.collection_display_start_date, new_off = decode_date_str(data, off) + off += new_off + + self.collection_empty_frame_display_flag = decode_byte(data, off) + off += BYTE_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "HeroLogData": + ret = cls(b"\x00" * 99, 0) + ret.hero_log_id = data['HeroLogId'] + ret.chara_id = data['CharaId'] + ret.name = data['Name'] + ret.nickname = data['Nickname'] + ret.rarity = data['Rarity'] + ret.weapon_type_id = data['WeaponTypeId'] + ret.hero_log_role_id = data['HeroLogRoleId'] + ret.costume_type_id = data['CostumeTypeId'] + ret.unit_id = data['UnitId'] + ret.default_equipment_id1 = data['DefaultEquipmentId1'] + ret.default_equipment_id2 = data['DefaultEquipmentId2'] + ret.skill_table_sub_id = data['SkillTableSubId'] + ret.hp_min = data['HpMin'] + ret.hp_max = data['HpMax'] + ret.str_min = data['StrMin'] + ret.str_max = data['StrMax'] + ret.vit_min = data['VitMin'] + ret.vit_max = data['VitMax'] + ret.int_min = data['IntMin'] + ret.int_max = data['IntMax'] + ret.property1_property_id = data['Property1PropertyId'] + ret.property1_value1 = data['Property1Value1'] + ret.property1_value2 = data['Property1Value2'] + ret.property2_property_id = data['Property2PropertyId'] + ret.property2_value1 = data['Property2Value1'] + ret.property2_value2 = data['Property2Value2'] + ret.property3_property_id = data['Property3PropertyId'] + ret.property3_value1 = data['Property3Value1'] + ret.property3_value2 = data['Property3Value2'] + ret.property4_property_id = data['Property4PropertyId'] + ret.property4_value1 = data['Property4Value1'] + ret.property4_value2 = data['Property4Value2'] + ret.flavor_text = data['FlavorText'] + ret.sale_price = data['SalePrice'] + ret.composition_exp = data['CompositionExp'] + ret.awakening_exp = data['AwakeningExp'] + ret.slot4_unlock_level = data['Slot4UnlockLevel'] + ret.slot5_unlock_level = data['Slot5UnlockLevel'] + ret.cut_in_image = data['CutinImage'] + ret.cut_in_image_awake = data['CutinImageAwake'] + ret.cut_in_upper_side_text = data['CutinUpperSideText'] + ret.chara_comment_image = data['CharaCommentImage'] + ret.chara_comment_image_awake = data['CharaCommentImageAwake'] + ret.quest_start_introduce = data['QuestStartIntroduce'] + ret.quest_start_introduce_awake = data['QuestStartIntroduceAwake'] + ret.quest_chara_icon = data['QuestCharaIcon'] + ret.quest_chara_icon_awake = data['QuestCharaIconAwake'] + ret.quest_chara_icon_loss = data['QuestCharaIconLoss'] + ret.quest_chara_icon_awake_loss = data['QuestCharaIconAwakeLoss'] + ret.collection_display_start_date = data['CollectionDisplayStartDate'] + ret.collection_empty_frame_display_flag = data['CollectionEmptyFrameDisplayFlag'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.hero_log_id) \ + + encode_short(self.chara_id) \ + + encode_str(self.name) \ + + encode_str(self.nickname) \ + + encode_byte(self.rarity) \ + + encode_short(self.weapon_type_id) \ + + encode_short(self.hero_log_role_id) \ + + encode_short(self.costume_type_id) \ + + encode_int(self.unit_id) \ + + encode_int(self.default_equipment_id1) \ + + encode_int(self.default_equipment_id2) \ + + encode_int(self.skill_table_sub_id) \ + + encode_int(self.hp_min) \ + + encode_int(self.hp_max) \ + + encode_int(self.str_min) \ + + encode_int(self.str_max) \ + + encode_int(self.vit_min) \ + + encode_int(self.vit_max) \ + + encode_int(self.int_min) \ + + encode_int(self.int_max) \ + + encode_int(self.property1_property_id) \ + + encode_int(self.property1_value1) \ + + encode_int(self.property1_value2) \ + + encode_int(self.property2_property_id) \ + + encode_int(self.property2_value1) \ + + encode_int(self.property2_value2) \ + + encode_int(self.property3_property_id) \ + + encode_int(self.property3_value1) \ + + encode_int(self.property3_value2) \ + + encode_int(self.property4_property_id) \ + + encode_int(self.property4_value1) \ + + encode_int(self.property4_value2) \ + + encode_str(self.flavor_text) \ + + encode_int(self.sale_price) \ + + encode_int(self.composition_exp) \ + + encode_int(self.awakening_exp) \ + + encode_int(self.slot4_unlock_level) \ + + encode_int(self.slot5_unlock_level) \ + + encode_str(self.cut_in_image) \ + + encode_str(self.cut_in_image_awake) \ + + encode_byte(self.cut_in_upper_side_text) \ + + encode_str(self.chara_comment_image) \ + + encode_str(self.chara_comment_image_awake) \ + + encode_str(self.quest_start_introduce) \ + + encode_str(self.quest_start_introduce_awake) \ + + encode_str(self.quest_chara_icon) \ + + encode_str(self.quest_chara_icon_awake) \ + + encode_str(self.quest_chara_icon_loss) \ + + encode_str(self.quest_chara_icon_awake_loss) \ + + encode_date_str(self.collection_display_start_date) \ + + encode_byte(self.collection_empty_frame_display_flag) + +class HeroLogLevelData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.level = decode_short(data, off) + off += SHORT_OFF + + self.require_exp = decode_int(data, off) + off += INT_OFF + + self.total_exp = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "HeroLogLevelData": + ret = cls(b"\x00" * 99, 0) + ret.level = data['HeroLogLevelId'] + ret.require_exp = data['RequireExp'] + ret.total_exp = data['TotalExp'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.level) \ + + encode_int(self.require_exp) \ + + encode_int(self.total_exp) + +class HeroLogRoleData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.hero_log_role_id = decode_short(data, off) + off += SHORT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "HeroLogRoleData": + ret = cls(b"\x00" * 99, 0) + ret.hero_log_role_id = data['HeroLogRoleId'] + ret.name = data['Name'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.hero_log_role_id) \ + + encode_str(self.name) + +class HeroLogTrustRankData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.rank = decode_short(data, off) + off += SHORT_OFF + + self.total_exp = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "HeroLogTrustRankData": + ret = cls(b"\x00" * 99, 0) + ret.rank = data['HeroLogTrustRankId'] + ret.total_exp = data['TotalExp'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.rank) \ + + encode_int(self.total_exp) + +class CharaData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.chara_id = decode_short(data, off) + off += SHORT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self.roma, new_off = decode_str(data, off) + off += new_off + + self.gender = decode_short(data, off) + off += SHORT_OFF + + self.real_name, new_off = decode_str(data, off) + off += new_off + + self.comment, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "CharaData": + ret = cls(b"\x00" * 99, 0) + ret.chara_id = data['CharaId'] + ret.name = data['Name'] + ret.roma = data['Roma'] + ret.gender = data['Gender'] + ret.real_name = data['RealName'] + ret.comment = data['Comment'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.chara_id) \ + + encode_str(self.name) \ + + encode_str(self.roma) \ + + encode_short(self.gender) \ + + encode_str(self.real_name) \ + + encode_str(self.comment) + +class CharaFriendlyRankData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.rank = decode_short(data, off) + off += SHORT_OFF + + self.total_exp = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "CharaFriendlyRankData": + ret = cls(b"\x00" * 99, 0) + ret.rank = data['CharaFriendlyRankId'] + ret.total_exp = data['TotalExp'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.rank) \ + + encode_int(self.total_exp) + +class EquipmentData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.equipment_id = decode_int(data, off) + off += INT_OFF + + self.equipment_type = decode_byte(data, off) + off += BYTE_OFF + + self.weapon_type_id = decode_short(data, off) + off += SHORT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self.rarity = decode_byte(data, off) + off += BYTE_OFF + + self.prefab, new_off = decode_str(data, off) + off += new_off + + self.power = decode_int(data, off) + off += INT_OFF + + self.strength_increment = decode_int(data, off) + off += INT_OFF + + self.skill_condition = decode_short(data, off) + off += SHORT_OFF + + self.property1_property_id = decode_int(data, off) + off += INT_OFF + + self.property1_value1 = decode_int(data, off) + off += INT_OFF + + self.property1_value2 = decode_int(data, off) + off += INT_OFF + + self.property2_property_id = decode_int(data, off) + off += INT_OFF + + self.property2_value1 = decode_int(data, off) + off += INT_OFF + + self.property2_value2 = decode_int(data, off) + off += INT_OFF + + self.property3_property_id = decode_int(data, off) + off += INT_OFF + + self.property3_value1 = decode_int(data, off) + off += INT_OFF + + self.property3_value2 = decode_int(data, off) + off += INT_OFF + + self.property4_property_id = decode_int(data, off) + off += INT_OFF + + self.property4_value1 = decode_int(data, off) + off += INT_OFF + + self.property4_value2 = decode_int(data, off) + off += INT_OFF + + self.sale_price = decode_int(data, off) + off += INT_OFF + + self.composition_exp = decode_int(data, off) + off += INT_OFF + + self.awakening_exp = decode_int(data, off) + off += INT_OFF + + self.flavor_text, new_off = decode_str(data, off) + off += new_off + + self.collection_display_start_date, new_off = decode_date_str(data, off) + off += new_off + + self.collection_empty_frame_display_flag = decode_byte(data, off) + off += BYTE_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "EquipmentData": + ret = cls(b"\x00" * 99, 0) + ret.equipment_id = data['EquipmentId'] + ret.equipment_type = data['EquipmentType'] + ret.weapon_type_id = data['WeaponTypeId'] + ret.name = data['Name'] + ret.rarity = data['Rarity'] + ret.prefab = data['Prefab'] + ret.power = data['Power'] + ret.strength_increment = data['StrengthIncrement'] + ret.skill_condition = data['SkillCondition'] + ret.property1_property_id = data['Property1PropertyId'] + ret.property1_value1 = data['Property1Value1'] + ret.property1_value2 = data['Property1Value2'] + ret.property2_property_id = data['Property2PropertyId'] + ret.property2_value1 = data['Property2Value1'] + ret.property2_value2 = data['Property2Value2'] + ret.property3_property_id = data['Property3PropertyId'] + ret.property3_value1 = data['Property3Value1'] + ret.property3_value2 = data['Property3Value2'] + ret.property4_property_id = data['Property4PropertyId'] + ret.property4_value1 = data['Property4Value1'] + ret.property4_value2 = data['Property4Value2'] + ret.sale_price = data['SalePrice'] + ret.composition_exp = data['CompositionExp'] + ret.awakening_exp = data['AwakeningExp'] + ret.flavor_text = data['FlavorText'] + ret.collection_display_start_date = data['CollectionDisplayStartDate'] + ret.collection_empty_frame_display_flag = data['CollectionEmptyFrameDisplayFlag'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.equipment_id) \ + + encode_byte(self.equipment_type) \ + + encode_short(self.weapon_type_id) \ + + encode_str(self.name) \ + + encode_byte(self.rarity) \ + + encode_str(self.prefab) \ + + encode_int(self.power) \ + + encode_int(self.strength_increment) \ + + encode_short(self.skill_condition) \ + + encode_int(self.property1_property_id) \ + + encode_int(self.property1_value1) \ + + encode_int(self.property1_value2) \ + + encode_int(self.property2_property_id) \ + + encode_int(self.property2_value1) \ + + encode_int(self.property2_value2) \ + + encode_int(self.property3_property_id) \ + + encode_int(self.property3_value1) \ + + encode_int(self.property3_value2) \ + + encode_int(self.property4_property_id) \ + + encode_int(self.property4_value1) \ + + encode_int(self.property4_value2) \ + + encode_int(self.sale_price) \ + + encode_int(self.composition_exp) \ + + encode_int(self.awakening_exp) \ + + encode_str(self.flavor_text) \ + + encode_date_str(self.collection_display_start_date) \ + + encode_byte(self.collection_empty_frame_display_flag) + +class EquipmentLevelData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.level = decode_short(data, off) + off += SHORT_OFF + + self.require_exp = decode_int(data, off) + off += INT_OFF + + self.total_exp = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "EquipmentLevelData": + ret = cls(b"\x00" * 99, 0) + ret.level = data['EquipmentLevelId'] + ret.require_exp = data['RequireExp'] + ret.total_exp = data['TotalExp'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.level) \ + + encode_int(self.require_exp) \ + + encode_int(self.total_exp) + +class WeaponTypeData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.weapon_type_id = decode_short(data, off) + off += SHORT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self.action_behaviour, new_off = decode_str(data, off) + off += new_off + + self.sound_behaviour, new_off = decode_str(data, off) + off += new_off + + self.physics_attr = decode_short(data, off) + off += SHORT_OFF + + self.main_equip = decode_short(data, off) + off += SHORT_OFF + + self.sub_equip1 = decode_short(data, off) + off += SHORT_OFF + + self.sub_equip2 = decode_short(data, off) + off += SHORT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "WeaponTypeData": + ret = cls(b"\x00" * 99, 0) + ret.weapon_type_id = data['WeaponTypeId'] + ret.name = data['Name'] + ret.action_behaviour = data['ActionBehaviour'] + ret.sound_behaviour = data['SoundBehaviour'] + ret.physics_attr = data['PhysicsAttr'] + ret.main_equip = data['MainEquip'] + ret.sub_equip1 = data['SubEquip1'] + ret.sub_equip2 = data['SubEquip2'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.weapon_type_id) \ + + encode_str(self.name) \ + + encode_str(self.action_behaviour) \ + + encode_str(self.sound_behaviour) \ + + encode_short(self.physics_attr) \ + + encode_short(self.main_equip) \ + + encode_short(self.sub_equip1) \ + + encode_short(self.sub_equip2) + +class ItemData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.item_id = decode_int(data, off) + off += INT_OFF + + self.item_type = decode_int(data, off) + off += INT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self.rarity = decode_byte(data, off) + off += BYTE_OFF + + self.value = decode_int(data, off) + off += INT_OFF + + self.property_id = decode_int(data, off) + off += INT_OFF + + self.property_value1_min, new_off = decode_str(data, off) + off += new_off + + self.property_value1_max, new_off = decode_str(data, off) + off += new_off + + self.property_value2_min, new_off = decode_str(data, off) + off += new_off + + self.property_value2_max, new_off = decode_str(data, off) + off += new_off + + self.flavor_text, new_off = decode_str(data, off) + off += new_off + + self.sale_price = decode_int(data, off) + off += INT_OFF + + self.item_icon, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "ItemData": + ret = cls(b"\x00" * 99, 0) + ret.item_id = data['ItemId'] + ret.item_type = data['ItemTypeId'] + ret.name = data['Name'] + ret.rarity = data['Rarity'] + ret.value = data['Value'] + ret.property_id = data['PropertyId'] + ret.property_value1_min = data['PropertyValue1Min'] + ret.property_value1_max = data['PropertyValue1Max'] + ret.property_value2_min = data['PropertyValue2Min'] + ret.property_value2_max = data['PropertyValue2Max'] + ret.flavor_text = data['FlavorText'] + ret.sale_price = data['SalePrice'] + ret.item_icon = data['ItemIcon'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.item_id) \ + + encode_int(self.item_type) \ + + encode_str(self.name) \ + + encode_byte(self.rarity) \ + + encode_int(self.value) \ + + encode_int(self.property_id) \ + + encode_str(self.property_value1_min) \ + + encode_str(self.property_value1_max) \ + + encode_str(self.property_value2_min) \ + + encode_str(self.property_value2_max) \ + + encode_str(self.flavor_text) \ + + encode_int(self.sale_price) \ + + encode_str(self.item_icon) + +class ItemTypeData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.item_type_id = decode_int(data, off) + off += INT_OFF + + self.item_type_name, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "ItemTypeData": + ret = cls(b"\x00" * 99, 0) + ret.item_type_id = data['ItemTypeId'] + ret.item_type_name = data['ItemTypeName'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.item_type_id) \ + + encode_str(self.item_type_name) + +class BuffItemData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.buff_item_id = decode_int(data, off) + off += INT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self.flavor_text, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "BuffItemData": + ret = cls(b"\x00" * 99, 0) + ret.buff_item_id = data['BuffItemId'] + ret.name = data['Name'] + ret.flavor_text = data['FlavorText'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.buff_item_id) \ + + encode_str(self.name) \ + + encode_str(self.flavor_text) + +class EnemyData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.enemy_id = decode_short(data, off) + off += SHORT_OFF + + self.unit_id = decode_int(data, off) + off += INT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "EnemyData": + ret = cls(b"\x00" * 99, 0) + ret.enemy_id = data['EnemyId'] + ret.unit_id = data['UnitId'] + ret.name = data['Name'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.enemy_id) \ + + encode_int(self.unit_id) \ + + encode_str(self.name) + +class EnemySetData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.enemy_set_id = decode_int(data, off) + off += INT_OFF + + self.enemy_set_sub_id = decode_int(data, off) + off += INT_OFF + + self.enemy_id = decode_int(data, off) + off += INT_OFF + + self.enemy_num = decode_short(data, off) + off += SHORT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "EnemySetData": + ret = cls(b"\x00" * 99, 0) + ret.enemy_set_id = data['EnemySetId'] + ret.enemy_set_sub_id = data['EnemySetSubId'] + ret.enemy_id = data['EnemyId'] + ret.enemy_num = data['EnemyNum'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.enemy_set_id) \ + + encode_int(self.enemy_set_sub_id) \ + + encode_int(self.enemy_id) \ + + encode_short(self.enemy_num) + +class EnemyKindData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.enemy_kind_id = decode_int(data, off) + off += INT_OFF + + self.enemy_category_id = decode_short(data, off) + off += SHORT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self.icon_filepath, new_off = decode_str(data, off) + off += new_off + + self.weak_physics_attr = decode_short(data, off) + off += SHORT_OFF + + self.weak_magic_attr = decode_short(data, off) + off += SHORT_OFF + + self.weak_text, new_off = decode_str(data, off) + off += new_off + + self.resist_text, new_off = decode_str(data, off) + off += new_off + + self.flavor_text, new_off = decode_str(data, off) + off += new_off + + self.collection_display_start_date, new_off = decode_date_str(data, off) + off += new_off + + self.collection_empty_frame_display_flag = decode_byte(data, off) + off += BYTE_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "EnemyKindData": + ret = cls(b"\x00" * 99, 0) + ret.enemy_kind_id = data['EnemyKindId'] + ret.enemy_category_id = data['EnemyCategoryId'] + ret.name = data['Name'] + ret.icon_filepath = data['IconFilepath'] + ret.weak_physics_attr = data['WeakPhysicsAttr'] + ret.weak_magic_attr = data['WeakMagicAttr'] + ret.weak_text = data['WeakText'] + ret.resist_text = data['ResistText'] + ret.flavor_text = data['FlavorText'] + ret.collection_display_start_date = data['CollectionDisplayStartDate'] + ret.collection_empty_frame_display_flag = data['CollectionEmptyFrameDisplayFlag'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.enemy_kind_id) \ + + encode_short(self.enemy_category_id) \ + + encode_str(self.name) \ + + encode_str(self.icon_filepath) \ + + encode_short(self.weak_physics_attr) \ + + encode_short(self.weak_magic_attr) \ + + encode_str(self.weak_text) \ + + encode_str(self.resist_text) \ + + encode_str(self.flavor_text) \ + + encode_date_str(self.collection_display_start_date) \ + + encode_byte(self.collection_empty_frame_display_flag) + +class EnemyCategoryData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.enemy_category_id = decode_short(data, off) + off += SHORT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self.detail_text, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "EnemyCategoryData": + ret = cls(b"\x00" * 99, 0) + ret.enemy_category_id = data['EnemyCategoryId'] + ret.name = data['Name'] + ret.detail_text = data['DetailText'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.enemy_category_id) \ + + encode_str(self.name) \ + + encode_str(self.detail_text) + +class UnitData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.unit_id = decode_int(data, off) + off += INT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self.prefab, new_off = decode_str(data, off) + off += new_off + + self.animator, new_off = decode_str(data, off) + off += new_off + + self.collision, new_off = decode_str(data, off) + off += new_off + + self.radius, new_off = decode_str(data, off) + off += new_off + + self.child_unit_id = decode_int(data, off) + off += INT_OFF + + self.stage_start_prefab, new_off = decode_str(data, off) + off += new_off + + self.stage_start_y, new_off = decode_str(data, off) + off += new_off + + self.stage_start_z, new_off = decode_str(data, off) + off += new_off + + self.stage_touch_y, new_off = decode_str(data, off) + off += new_off + + self.stage_touch_z, new_off = decode_str(data, off) + off += new_off + + self.playable = decode_byte(data, off) + off += BYTE_OFF + + self.monster = decode_byte(data, off) + off += BYTE_OFF + + self.gimmick = decode_byte(data, off) + off += BYTE_OFF + + self.anger = decode_byte(data, off) + off += BYTE_OFF + + self.commander = decode_byte(data, off) + off += BYTE_OFF + + self.isolated = decode_byte(data, off) + off += BYTE_OFF + + self.base_behaviour, new_off = decode_str(data, off) + off += new_off + + self.sound_behaviour, new_off = decode_str(data, off) + off += new_off + + self.weapon_type = decode_int(data, off) + off += INT_OFF + + self.poison_time_coef, new_off = decode_str(data, off) + off += new_off + + self.paralysis_time_coef, new_off = decode_str(data, off) + off += new_off + + self.sealed_time_coef, new_off = decode_str(data, off) + off += new_off + + self.question_time_coef, new_off = decode_str(data, off) + off += new_off + + self.blue_rose_time_coef, new_off = decode_str(data, off) + off += new_off + + self.charm_time_coef, new_off = decode_str(data, off) + off += new_off + + self.burning_time_coef, new_off = decode_str(data, off) + off += new_off + + self.invalid_quake = decode_byte(data, off) + off += BYTE_OFF + + self.guard_coef, new_off = decode_str(data, off) + off += new_off + + self.just_guard_time, new_off = decode_str(data, off) + off += new_off + + self.flip_hp = decode_short(data, off) + off += SHORT_OFF + + self.down_hp = decode_short(data, off) + off += SHORT_OFF + + self.flip_recover_time, new_off = decode_str(data, off) + off += new_off + + self.down_recover_time, new_off = decode_str(data, off) + off += new_off + + self.down_time, new_off = decode_str(data, off) + off += new_off + + self.loss_damage = decode_short(data, off) + off += SHORT_OFF + + self.loss_time, new_off = decode_str(data, off) + off += new_off + + self.move_speed, new_off = decode_str(data, off) + off += new_off + + self.move_speed_angry, new_off = decode_str(data, off) + off += new_off + + self.rot_speed, new_off = decode_str(data, off) + off += new_off + + self.rot_speed_angry, new_off = decode_str(data, off) + off += new_off + + self.range_min, new_off = decode_str(data, off) + off += new_off + + self.range_max, new_off = decode_str(data, off) + off += new_off + + self.range_min_angry, new_off = decode_str(data, off) + off += new_off + + self.range_max_angry, new_off = decode_str(data, off) + off += new_off + + self.back_step_speed, new_off = decode_str(data, off) + off += new_off + + self.back_step_speed_angry, new_off = decode_str(data, off) + off += new_off + + self.back_step_cool_time, new_off = decode_str(data, off) + off += new_off + + self.monster_type = decode_short(data, off) + off += SHORT_OFF + + self.monster_type_angry = decode_short(data, off) + off += SHORT_OFF + + self.vs_boss_type = decode_short(data, off) + off += SHORT_OFF + + self.boss_camera_height, new_off = decode_str(data, off) + off += new_off + + self.boss_camera_near_distance, new_off = decode_str(data, off) + off += new_off + + self.boss_camera_far_distance, new_off = decode_str(data, off) + off += new_off + + self.boss_camera_near_range, new_off = decode_str(data, off) + off += new_off + + self.boss_camera_far_range, new_off = decode_str(data, off) + off += new_off + + self.search_range, new_off = decode_str(data, off) + off += new_off + + self.search_angle, new_off = decode_str(data, off) + off += new_off + + self.lost_range, new_off = decode_str(data, off) + off += new_off + + self.home_range, new_off = decode_str(data, off) + off += new_off + + self.roar_id, new_off = decode_str(data, off) + off += new_off + + self.auto_attack_range, new_off = decode_str(data, off) + off += new_off + + self.auto_attack_interval, new_off = decode_str(data, off) + off += new_off + + self.find_priority = decode_short(data, off) + off += SHORT_OFF + + self.find_range_min = decode_short(data, off) + off += SHORT_OFF + + self.find_range_max = decode_short(data, off) + off += SHORT_OFF + + self.leave_range = decode_short(data, off) + off += SHORT_OFF + + self.show_arrow = decode_byte(data, off) + off += BYTE_OFF + + self.find_id, new_off = decode_str(data, off) + off += new_off + + self.leave_id, new_off = decode_str(data, off) + off += new_off + + self.defeat_id, new_off = decode_str(data, off) + off += new_off + + self.warfare_id, new_off = decode_str(data, off) + off += new_off + + self.angry_id, new_off = decode_str(data, off) + off += new_off + + self.bad_state_bone, new_off = decode_str(data, off) + off += new_off + + self.bad_state_offset_x, new_off = decode_str(data, off) + off += new_off + + self.bad_state_offset_y, new_off = decode_str(data, off) + off += new_off + + self.bad_state_offset_z, new_off = decode_str(data, off) + off += new_off + + self.drop_offset_y, new_off = decode_str(data, off) + off += new_off + + self.col_drop_percentage, new_off = decode_str(data, off) + off += new_off + + self.col_drop_amount = decode_short(data, off) + off += SHORT_OFF + + self.vp_drop_percentage, new_off = decode_str(data, off) + off += new_off + + self.vp_drop_amount = decode_short(data, off) + off += SHORT_OFF + + self.enemy_kind_id = decode_int(data, off) + off += INT_OFF + + self.defrag_match_league_point = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "UnitData": + ret = cls(b"\x00" * 99, 0) + ret.unit_id = data['UnitId'] + ret.name = data['Name'] + ret.prefab = data['Prefab'] + ret.animator = data['Animator'] + ret.collision = data['Collision'] + ret.radius = data['Radius'] + ret.child_unit_id = data['ChildUnitId'] + ret.stage_start_prefab = data['StageStartPrefab'] + ret.stage_start_y = data['StageStartY'] + ret.stage_start_z = data['StageStartZ'] + ret.stage_touch_y = data['StageTouchY'] + ret.stage_touch_z = data['StageTouchZ'] + ret.playable = data['Playable'] + ret.monster = data['Monster'] + ret.gimmick = data['Gimmick'] + ret.anger = data['Anger'] + ret.commander = data['Commander'] + ret.isolated = data['Isolated'] + ret.base_behaviour = data['BaseBehaviour'] + ret.sound_behaviour = data['SoundBehaviour'] + ret.weapon_type = data['WeaponType'] + ret.poison_time_coef = data['PoisonTimeCoef'] + ret.paralysis_time_coef = data['ParalysisTimeCoef'] + ret.sealed_time_coef = data['SealedTimeCoef'] + ret.question_time_coef = data['QuestionTimeCoef'] + ret.blue_rose_time_coef = data['BlueRoseTimeCoef'] + ret.charm_time_coef = data['CharmTimeCoef'] + ret.burning_time_coef = data['BurningTimeCoef'] + ret.invalid_quake = data['InvalidQuake'] + ret.guard_coef = data['GuardCoef'] + ret.just_guard_time = data['JustGuardTime'] + ret.flip_hp = data['FlipHp'] + ret.down_hp = data['DownHp'] + ret.flip_recover_time = data['FlipRecoverTime'] + ret.down_recover_time = data['DownRecoverTime'] + ret.down_time = data['DownTime'] + ret.loss_damage = data['LossDamage'] + ret.loss_time = data['LossTime'] + ret.move_speed = data['MoveSpeed'] + ret.move_speed_angry = data['MoveSpeedAngry'] + ret.rot_speed = data['RotSpeed'] + ret.rot_speed_angry = data['RotSpeedAngry'] + ret.range_min = data['RangeMin'] + ret.range_max = data['RangeMax'] + ret.range_min_angry = data['RangeMinAngry'] + ret.range_max_angry = data['RangeMaxAngry'] + ret.back_step_speed = data['BackStepSpeed'] + ret.back_step_speed_angry = data['BackStepSpeedAngry'] + ret.back_step_cool_time = data['BackStepCoolTime'] + ret.monster_type = data['MonsterType'] + ret.monster_type_angry = data['MonsterTypeAngry'] + ret.vs_boss_type = data['VsBossType'] + ret.boss_camera_height = data['BossCameraHeight'] + ret.boss_camera_near_distance = data['BossCameraNearDistance'] + ret.boss_camera_far_distance = data['BossCameraFarDistance'] + ret.boss_camera_near_range = data['BossCameraNearRange'] + ret.boss_camera_far_range = data['BossCameraFarRange'] + ret.search_range = data['SearchRange'] + ret.search_angle = data['SearchAngle'] + ret.lost_range = data['LostRange'] + ret.home_range = data['HomeRange'] + ret.roar_id = data['RoarId'] + ret.auto_attack_range = data['AutoAttackRange'] + ret.auto_attack_interval = data['AutoAttackInterval'] + ret.find_priority = data['FindPriority'] + ret.find_range_min = data['FindRangeMin'] + ret.find_range_max = data['FindRangeMax'] + ret.leave_range = data['LeaveRange'] + ret.show_arrow = data['ShowArrow'] + ret.find_id = data['FindId'] + ret.leave_id = data['LeaveId'] + ret.defeat_id = data['DefeatId'] + ret.warfare_id = data['WarfareId'] + ret.angry_id = data['AngryId'] + ret.bad_state_bone = data['BadStateBone'] + ret.bad_state_offset_x = data['BadStateOffsetX'] + ret.bad_state_offset_y = data['BadStateOffsetY'] + ret.bad_state_offset_z = data['BadStateOffsetZ'] + ret.drop_offset_y = data['DropOffsetY'] + ret.col_drop_percentage = data['ColDropPercentage'] + ret.col_drop_amount = data['ColDropAmount'] + ret.vp_drop_percentage = data['VpDropPercentage'] + ret.vp_drop_amount = data['VpDropAmount'] + ret.enemy_kind_id = data['EnemyKindId'] + ret.defrag_match_league_point = data['DefragMatchLeaguePoint'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.unit_id) \ + + encode_str(self.name) \ + + encode_str(self.prefab) \ + + encode_str(self.animator) \ + + encode_str(self.collision) \ + + encode_str(self.radius) \ + + encode_int(self.child_unit_id) \ + + encode_str(self.stage_start_prefab) \ + + encode_str(self.stage_start_y) \ + + encode_str(self.stage_start_z) \ + + encode_str(self.stage_touch_y) \ + + encode_str(self.stage_touch_z) \ + + encode_byte(self.playable) \ + + encode_byte(self.monster) \ + + encode_byte(self.gimmick) \ + + encode_byte(self.anger) \ + + encode_byte(self.commander) \ + + encode_byte(self.isolated) \ + + encode_str(self.base_behaviour) \ + + encode_str(self.sound_behaviour) \ + + encode_int(self.weapon_type) \ + + encode_str(self.poison_time_coef) \ + + encode_str(self.paralysis_time_coef) \ + + encode_str(self.sealed_time_coef) \ + + encode_str(self.question_time_coef) \ + + encode_str(self.blue_rose_time_coef) \ + + encode_str(self.charm_time_coef) \ + + encode_str(self.burning_time_coef) \ + + encode_byte(self.invalid_quake) \ + + encode_str(self.guard_coef) \ + + encode_str(self.just_guard_time) \ + + encode_short(self.flip_hp) \ + + encode_short(self.down_hp) \ + + encode_str(self.flip_recover_time) \ + + encode_str(self.down_recover_time) \ + + encode_str(self.down_time) \ + + encode_short(self.loss_damage) \ + + encode_str(self.loss_time) \ + + encode_str(self.move_speed) \ + + encode_str(self.move_speed_angry) \ + + encode_str(self.rot_speed) \ + + encode_str(self.rot_speed_angry) \ + + encode_str(self.range_min) \ + + encode_str(self.range_max) \ + + encode_str(self.range_min_angry) \ + + encode_str(self.range_max_angry) \ + + encode_str(self.back_step_speed) \ + + encode_str(self.back_step_speed_angry) \ + + encode_str(self.back_step_cool_time) \ + + encode_short(self.monster_type) \ + + encode_short(self.monster_type_angry) \ + + encode_short(self.vs_boss_type) \ + + encode_str(self.boss_camera_height) \ + + encode_str(self.boss_camera_near_distance) \ + + encode_str(self.boss_camera_far_distance) \ + + encode_str(self.boss_camera_near_range) \ + + encode_str(self.boss_camera_far_range) \ + + encode_str(self.search_range) \ + + encode_str(self.search_angle) \ + + encode_str(self.lost_range) \ + + encode_str(self.home_range) \ + + encode_str(self.roar_id) \ + + encode_str(self.auto_attack_range) \ + + encode_str(self.auto_attack_interval) \ + + encode_short(self.find_priority) \ + + encode_short(self.find_range_min) \ + + encode_short(self.find_range_max) \ + + encode_short(self.leave_range) \ + + encode_byte(self.show_arrow) \ + + encode_str(self.find_id) \ + + encode_str(self.leave_id) \ + + encode_str(self.defeat_id) \ + + encode_str(self.warfare_id) \ + + encode_str(self.angry_id) \ + + encode_str(self.bad_state_bone) \ + + encode_str(self.bad_state_offset_x) \ + + encode_str(self.bad_state_offset_y) \ + + encode_str(self.bad_state_offset_z) \ + + encode_str(self.drop_offset_y) \ + + encode_str(self.col_drop_percentage) \ + + encode_short(self.col_drop_amount) \ + + encode_str(self.vp_drop_percentage) \ + + encode_short(self.vp_drop_amount) \ + + encode_int(self.enemy_kind_id) \ + + encode_int(self.defrag_match_league_point) + +class UnitGimmickData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.unit_gimmick_id = decode_int(data, off) + off += INT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self.gimmick_type = decode_int(data, off) + off += INT_OFF + + self.range, new_off = decode_str(data, off) + off += new_off + + self.crystal_grade = decode_int(data, off) + off += INT_OFF + + self.col = decode_int(data, off) + off += INT_OFF + + self.skill_exp = decode_int(data, off) + off += INT_OFF + + self.heal_rate, new_off = decode_str(data, off) + off += new_off + + self.gimmick_attack_id = decode_short(data, off) + off += SHORT_OFF + + self.interval, new_off = decode_str(data, off) + off += new_off + + self.summon_count = decode_int(data, off) + off += INT_OFF + + self.effect, new_off = decode_str(data, off) + off += new_off + + self.effect_bone, new_off = decode_str(data, off) + off += new_off + + self.effect_x, new_off = decode_str(data, off) + off += new_off + + self.effect_y, new_off = decode_str(data, off) + off += new_off + + self.effect_z, new_off = decode_str(data, off) + off += new_off + + self.se, new_off = decode_str(data, off) + off += new_off + + self.ground_se, new_off = decode_str(data, off) + off += new_off + + self.sub_state_idle, new_off = decode_str(data, off) + off += new_off + + self.sub_state_reaction, new_off = decode_str(data, off) + off += new_off + + self.sub_state_active, new_off = decode_str(data, off) + off += new_off + + self.sub_state_break, new_off = decode_str(data, off) + off += new_off + + self.time_sub_state_break, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "UnitGimmickData": + ret = cls(b"\x00" * 99, 0) + ret.unit_gimmick_id = data['UnitGimmickId'] + ret.name = data['Name'] + ret.gimmick_type = data['GimmickType'] + ret.range = data['Range'] + ret.crystal_grade = data['CrystalGrade'] + ret.col = data['Col'] + ret.skill_exp = data['SkillExp'] + ret.heal_rate = data['HealRate'] + ret.gimmick_attack_id = data['GimmickAttackId'] + ret.interval = data['Interval'] + ret.summon_count = data['SummonCount'] + ret.effect = data['Effect'] + ret.effect_bone = data['EffectBone'] + ret.effect_x = data['EffectX'] + ret.effect_y = data['EffectY'] + ret.effect_z = data['EffectZ'] + ret.se = data['Se'] + ret.ground_se = data['GroundSe'] + ret.sub_state_idle = data['SubStateIdle'] + ret.sub_state_reaction = data['SubStateReaction'] + ret.sub_state_active = data['SubStateActive'] + ret.sub_state_break = data['SubStateBreak'] + ret.time_sub_state_break = data['TimeSubStateBreak'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.unit_gimmick_id) \ + + encode_str(self.name) \ + + encode_int(self.gimmick_type) \ + + encode_str(self.range) \ + + encode_int(self.crystal_grade) \ + + encode_int(self.col) \ + + encode_int(self.skill_exp) \ + + encode_str(self.heal_rate) \ + + encode_short(self.gimmick_attack_id) \ + + encode_str(self.interval) \ + + encode_int(self.summon_count) \ + + encode_str(self.effect) \ + + encode_str(self.effect_bone) \ + + encode_str(self.effect_x) \ + + encode_str(self.effect_y) \ + + encode_str(self.effect_z) \ + + encode_str(self.se) \ + + encode_str(self.ground_se) \ + + encode_str(self.sub_state_idle) \ + + encode_str(self.sub_state_reaction) \ + + encode_str(self.sub_state_active) \ + + encode_str(self.sub_state_break) \ + + encode_str(self.time_sub_state_break) + +class UnitCollisionData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.unit_collision_id = decode_int(data, off) + off += INT_OFF + + self.keyword, new_off = decode_str(data, off) + off += new_off + + self.name, new_off = decode_str(data, off) + off += new_off + + self.bone, new_off = decode_str(data, off) + off += new_off + + self.usually = decode_byte(data, off) + off += BYTE_OFF + + self.angry = decode_byte(data, off) + off += BYTE_OFF + + self.type_index = decode_short(data, off) + off += SHORT_OFF + + self.begin_x, new_off = decode_str(data, off) + off += new_off + + self.begin_y, new_off = decode_str(data, off) + off += new_off + + self.begin_z, new_off = decode_str(data, off) + off += new_off + + self.end_x, new_off = decode_str(data, off) + off += new_off + + self.end_y, new_off = decode_str(data, off) + off += new_off + + self.end_z, new_off = decode_str(data, off) + off += new_off + + self.radius, new_off = decode_str(data, off) + off += new_off + + self.slash_coef, new_off = decode_str(data, off) + off += new_off + + self.strike_coef, new_off = decode_str(data, off) + off += new_off + + self.thrust_coef, new_off = decode_str(data, off) + off += new_off + + self.fire_coef, new_off = decode_str(data, off) + off += new_off + + self.water_coef, new_off = decode_str(data, off) + off += new_off + + self.air_coef, new_off = decode_str(data, off) + off += new_off + + self.earth_coef, new_off = decode_str(data, off) + off += new_off + + self.holy_coef, new_off = decode_str(data, off) + off += new_off + + self.dark_coef, new_off = decode_str(data, off) + off += new_off + + self.poison_coef, new_off = decode_str(data, off) + off += new_off + + self.paralysis_coef, new_off = decode_str(data, off) + off += new_off + + self.sealed_coef, new_off = decode_str(data, off) + off += new_off + + self.question_coef, new_off = decode_str(data, off) + off += new_off + + self.blue_rose_coef, new_off = decode_str(data, off) + off += new_off + + self.charm_coef, new_off = decode_str(data, off) + off += new_off + + self.burning_coef, new_off = decode_str(data, off) + off += new_off + + self.flip_coef, new_off = decode_str(data, off) + off += new_off + + self.down_coef, new_off = decode_str(data, off) + off += new_off + + self.knock_back_coef, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "UnitCollisionData": + ret = cls(b"\x00" * 99, 0) + ret.unit_collision_id = data['UnitCollisionId'] + ret.keyword = data['Keyword'] + ret.name = data['Name'] + ret.bone = data['Bone'] + ret.usually = data['Usually'] + ret.angry = data['Angry'] + ret.type_index = data['TypeIndex'] + ret.begin_x = data['BeginX'] + ret.begin_y = data['BeginY'] + ret.begin_z = data['BeginZ'] + ret.end_x = data['EndX'] + ret.end_y = data['EndY'] + ret.end_z = data['EndZ'] + ret.radius = data['Radius'] + ret.slash_coef = data['SlashCoef'] + ret.strike_coef = data['StrikeCoef'] + ret.thrust_coef = data['ThrustCoef'] + ret.fire_coef = data['FireCoef'] + ret.water_coef = data['WaterCoef'] + ret.air_coef = data['AirCoef'] + ret.earth_coef = data['EarthCoef'] + ret.holy_coef = data['HolyCoef'] + ret.dark_coef = data['DarkCoef'] + ret.poison_coef = data['PoisonCoef'] + ret.paralysis_coef = data['ParalysisCoef'] + ret.sealed_coef = data['SealedCoef'] + ret.question_coef = data['QuestionCoef'] + ret.blue_rose_coef = data['BlueRoseCoef'] + ret.charm_coef = data['CharmCoef'] + ret.burning_coef = data['BurningCoef'] + ret.flip_coef = data['FlipCoef'] + ret.down_coef = data['DownCoef'] + ret.knock_back_coef = data['KnockBackCoef'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.unit_collision_id) \ + + encode_str(self.keyword) \ + + encode_str(self.name) \ + + encode_str(self.bone) \ + + encode_byte(self.usually) \ + + encode_byte(self.angry) \ + + encode_short(self.type_index) \ + + encode_str(self.begin_x) \ + + encode_str(self.begin_y) \ + + encode_str(self.begin_z) \ + + encode_str(self.end_x) \ + + encode_str(self.end_y) \ + + encode_str(self.end_z) \ + + encode_str(self.radius) \ + + encode_str(self.slash_coef) \ + + encode_str(self.strike_coef) \ + + encode_str(self.thrust_coef) \ + + encode_str(self.fire_coef) \ + + encode_str(self.water_coef) \ + + encode_str(self.air_coef) \ + + encode_str(self.earth_coef) \ + + encode_str(self.holy_coef) \ + + encode_str(self.dark_coef) \ + + encode_str(self.poison_coef) \ + + encode_str(self.paralysis_coef) \ + + encode_str(self.sealed_coef) \ + + encode_str(self.question_coef) \ + + encode_str(self.blue_rose_coef) \ + + encode_str(self.charm_coef) \ + + encode_str(self.burning_coef) \ + + encode_str(self.flip_coef) \ + + encode_str(self.down_coef) \ + + encode_str(self.knock_back_coef) + +class UnitPowerData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.unit_power_id = decode_int(data, off) + off += INT_OFF + + self.unit_id = decode_int(data, off) + off += INT_OFF + + self.lv = decode_int(data, off) + off += INT_OFF + + self.param_hp = decode_int(data, off) + off += INT_OFF + + self.param_str = decode_int(data, off) + off += INT_OFF + + self.param_vit = decode_int(data, off) + off += INT_OFF + + self.param_int = decode_int(data, off) + off += INT_OFF + + self.exp = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "UnitPowerData": + ret = cls(b"\x00" * 99, 0) + ret.unit_power_id = data['UnitPowerId'] + ret.unit_id = data['UnitId'] + ret.lv = data['Lv'] + ret.param_hp = data['ParamHp'] + ret.param_str = data['ParamStr'] + ret.param_vit = data['ParamVit'] + ret.param_int = data['ParamInt'] + ret.exp = data['Exp'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.unit_power_id) \ + + encode_int(self.unit_id) \ + + encode_int(self.lv) \ + + encode_int(self.param_hp) \ + + encode_int(self.param_str) \ + + encode_int(self.param_vit) \ + + encode_int(self.param_int) \ + + encode_int(self.exp) + +class GimmickAttackData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.gimmick_attack_id = decode_short(data, off) + off += SHORT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self.hit_enemy = decode_byte(data, off) + off += BYTE_OFF + + self.hit_friend = decode_byte(data, off) + off += BYTE_OFF + + self.attack_coef, new_off = decode_str(data, off) + off += new_off + + self.heal_coef, new_off = decode_str(data, off) + off += new_off + + self.flip_damage = decode_short(data, off) + off += SHORT_OFF + + self.down_damage = decode_short(data, off) + off += SHORT_OFF + + self.poison_incidence, new_off = decode_str(data, off) + off += new_off + + self.paralysis_incidence, new_off = decode_str(data, off) + off += new_off + + self.sealed_incidence, new_off = decode_str(data, off) + off += new_off + + self.question_incidence, new_off = decode_str(data, off) + off += new_off + + self.blue_rose_incidence, new_off = decode_str(data, off) + off += new_off + + self.charm_incidence, new_off = decode_str(data, off) + off += new_off + + self.burning_incidence, new_off = decode_str(data, off) + off += new_off + + self.knock_back, new_off = decode_str(data, off) + off += new_off + + self.knock_back_g, new_off = decode_str(data, off) + off += new_off + + self.forward_knock_back = decode_byte(data, off) + off += BYTE_OFF + + self.break_interval, new_off = decode_str(data, off) + off += new_off + + self.attack_type = decode_short(data, off) + off += SHORT_OFF + + self.physics_attr = decode_short(data, off) + off += SHORT_OFF + + self.magic_attr = decode_short(data, off) + off += SHORT_OFF + + self.hit_se, new_off = decode_str(data, off) + off += new_off + + self.weak_se, new_off = decode_str(data, off) + off += new_off + + self.resist_se, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "GimmickAttackData": + ret = cls(b"\x00" * 99, 0) + ret.gimmick_attack_id = data['GimmickAttackId'] + ret.name = data['Name'] + ret.hit_enemy = data['HitEnemy'] + ret.hit_friend = data['HitFriend'] + ret.attack_coef = data['AttackCoef'] + ret.heal_coef = data['HealCoef'] + ret.flip_damage = data['FlipDamage'] + ret.down_damage = data['DownDamage'] + ret.poison_incidence = data['PoisonIncidence'] + ret.paralysis_incidence = data['ParalysisIncidence'] + ret.sealed_incidence = data['SealedIncidence'] + ret.question_incidence = data['QuestionIncidence'] + ret.blue_rose_incidence = data['BlueRoseIncidence'] + ret.charm_incidence = data['CharmIncidence'] + ret.burning_incidence = data['BurningIncidence'] + ret.knock_back = data['KnockBack'] + ret.knock_back_g = data['KnockBackG'] + ret.forward_knock_back = data['ForwardKnockBack'] + ret.break_interval = data['BreakInterval'] + ret.attack_type = data['AttackType'] + ret.physics_attr = data['PhysicsAttr'] + ret.magic_attr = data['MagicAttr'] + ret.hit_se = data['HitSe'] + ret.weak_se = data['WeakSe'] + ret.resist_se = data['ResistSe'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.gimmick_attack_id) \ + + encode_str(self.name) \ + + encode_byte(self.hit_enemy) \ + + encode_byte(self.hit_friend) \ + + encode_str(self.attack_coef) \ + + encode_str(self.heal_coef) \ + + encode_short(self.flip_damage) \ + + encode_short(self.down_damage) \ + + encode_str(self.poison_incidence) \ + + encode_str(self.paralysis_incidence) \ + + encode_str(self.sealed_incidence) \ + + encode_str(self.question_incidence) \ + + encode_str(self.blue_rose_incidence) \ + + encode_str(self.charm_incidence) \ + + encode_str(self.burning_incidence) \ + + encode_str(self.knock_back) \ + + encode_str(self.knock_back_g) \ + + encode_byte(self.forward_knock_back) \ + + encode_str(self.break_interval) \ + + encode_short(self.attack_type) \ + + encode_short(self.physics_attr) \ + + encode_short(self.magic_attr) \ + + encode_str(self.hit_se) \ + + encode_str(self.weak_se) \ + + encode_str(self.resist_se) + +class CharaAttackData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.chara_attack_id = decode_short(data, off) + off += SHORT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self.attack_coef, new_off = decode_str(data, off) + off += new_off + + self.flip_damage = decode_short(data, off) + off += SHORT_OFF + + self.down_damage = decode_short(data, off) + off += SHORT_OFF + + self.poison_incidence, new_off = decode_str(data, off) + off += new_off + + self.paralysis_incidence, new_off = decode_str(data, off) + off += new_off + + self.sealed_incidence, new_off = decode_str(data, off) + off += new_off + + self.question_incidence, new_off = decode_str(data, off) + off += new_off + + self.blue_rose_incidence, new_off = decode_str(data, off) + off += new_off + + self.charm_incidence, new_off = decode_str(data, off) + off += new_off + + self.knock_back, new_off = decode_str(data, off) + off += new_off + + self.forward_knock_back = decode_byte(data, off) + off += BYTE_OFF + + self.break_interval, new_off = decode_str(data, off) + off += new_off + + self.weapon_slot = decode_short(data, off) + off += SHORT_OFF + + self.attack_type = decode_short(data, off) + off += SHORT_OFF + + self.physics_attr = decode_short(data, off) + off += SHORT_OFF + + self.magic_attr = decode_short(data, off) + off += SHORT_OFF + + self.spell_blast = decode_byte(data, off) + off += BYTE_OFF + + self.hit_se, new_off = decode_str(data, off) + off += new_off + + self.weak_se, new_off = decode_str(data, off) + off += new_off + + self.resist_se, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "CharaAttackData": + ret = cls(b"\x00" * 99, 0) + ret.chara_attack_id = data['CharaAttackId'] + ret.name = data['Name'] + ret.attack_coef = data['AttackCoef'] + ret.flip_damage = data['FlipDamage'] + ret.down_damage = data['DownDamage'] + ret.poison_incidence = data['PoisonIncidence'] + ret.paralysis_incidence = data['ParalysisIncidence'] + ret.sealed_incidence = data['SealedIncidence'] + ret.question_incidence = data['QuestionIncidence'] + ret.blue_rose_incidence = data['BlueRoseIncidence'] + ret.charm_incidence = data['CharmIncidence'] + ret.knock_back = data['KnockBack'] + ret.forward_knock_back = data['ForwardKnockBack'] + ret.break_interval = data['BreakInterval'] + ret.weapon_slot = data['WeaponSlot'] + ret.attack_type = data['AttackType'] + ret.physics_attr = data['PhysicsAttr'] + ret.magic_attr = data['MagicAttr'] + ret.spell_blast = data['SpellBlast'] + ret.hit_se = data['HitSe'] + ret.weak_se = data['WeakSe'] + ret.resist_se = data['ResistSe'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.chara_attack_id) \ + + encode_str(self.name) \ + + encode_str(self.attack_coef) \ + + encode_short(self.flip_damage) \ + + encode_short(self.down_damage) \ + + encode_str(self.poison_incidence) \ + + encode_str(self.paralysis_incidence) \ + + encode_str(self.sealed_incidence) \ + + encode_str(self.question_incidence) \ + + encode_str(self.blue_rose_incidence) \ + + encode_str(self.charm_incidence) \ + + encode_str(self.knock_back) \ + + encode_byte(self.forward_knock_back) \ + + encode_str(self.break_interval) \ + + encode_short(self.weapon_slot) \ + + encode_short(self.attack_type) \ + + encode_short(self.physics_attr) \ + + encode_short(self.magic_attr) \ + + encode_byte(self.spell_blast) \ + + encode_str(self.hit_se) \ + + encode_str(self.weak_se) \ + + encode_str(self.resist_se) + +class BossAttackData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.boss_attack_id = decode_short(data, off) + off += SHORT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self.attack_coef, new_off = decode_str(data, off) + off += new_off + + self.flip_damage = decode_short(data, off) + off += SHORT_OFF + + self.down_damage = decode_short(data, off) + off += SHORT_OFF + + self.poison_incidence, new_off = decode_str(data, off) + off += new_off + + self.sealed_incidence, new_off = decode_str(data, off) + off += new_off + + self.question_incidence, new_off = decode_str(data, off) + off += new_off + + self.reduce_max_hp_incidence, new_off = decode_str(data, off) + off += new_off + + self.burning_incidence, new_off = decode_str(data, off) + off += new_off + + self.quake = decode_byte(data, off) + off += BYTE_OFF + + self.can_parry = decode_byte(data, off) + off += BYTE_OFF + + self.knock_back, new_off = decode_str(data, off) + off += new_off + + self.knock_back_g, new_off = decode_str(data, off) + off += new_off + + self.forward_knock_back = decode_byte(data, off) + off += BYTE_OFF + + self.attack_type = decode_short(data, off) + off += SHORT_OFF + + self.physics_attr = decode_short(data, off) + off += SHORT_OFF + + self.magic_attr = decode_short(data, off) + off += SHORT_OFF + + self.spell_blast = decode_byte(data, off) + off += BYTE_OFF + + self.hit_se, new_off = decode_str(data, off) + off += new_off + + self.weak_se, new_off = decode_str(data, off) + off += new_off + + self.resist_se, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "BossAttackData": + ret = cls(b"\x00" * 99, 0) + ret.boss_attack_id = data['BossAttackId'] + ret.name = data['Name'] + ret.attack_coef = data['AttackCoef'] + ret.flip_damage = data['FlipDamage'] + ret.down_damage = data['DownDamage'] + ret.poison_incidence = data['PoisonIncidence'] + ret.sealed_incidence = data['SealedIncidence'] + ret.question_incidence = data['QuestionIncidence'] + ret.reduce_max_hp_incidence = data['ReduceMaxHpIncidence'] + ret.burning_incidence = data['BurningIncidence'] + ret.quake = data['Quake'] + ret.can_parry = data['CanParry'] + ret.knock_back = data['KnockBack'] + ret.knock_back_g = data['KnockBackG'] + ret.forward_knock_back = data['ForwardKnockBack'] + ret.attack_type = data['AttackType'] + ret.physics_attr = data['PhysicsAttr'] + ret.magic_attr = data['MagicAttr'] + ret.spell_blast = data['SpellBlast'] + ret.hit_se = data['HitSe'] + ret.weak_se = data['WeakSe'] + ret.resist_se = data['ResistSe'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.boss_attack_id) \ + + encode_str(self.name) \ + + encode_str(self.attack_coef) \ + + encode_short(self.flip_damage) \ + + encode_short(self.down_damage) \ + + encode_str(self.poison_incidence) \ + + encode_str(self.sealed_incidence) \ + + encode_str(self.question_incidence) \ + + encode_str(self.reduce_max_hp_incidence) \ + + encode_str(self.burning_incidence) \ + + encode_byte(self.quake) \ + + encode_byte(self.can_parry) \ + + encode_str(self.knock_back) \ + + encode_str(self.knock_back_g) \ + + encode_byte(self.forward_knock_back) \ + + encode_short(self.attack_type) \ + + encode_short(self.physics_attr) \ + + encode_short(self.magic_attr) \ + + encode_byte(self.spell_blast) \ + + encode_str(self.hit_se) \ + + encode_str(self.weak_se) \ + + encode_str(self.resist_se) + +class MonsterAttackData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.monster_attack_id = decode_short(data, off) + off += SHORT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self.attack_coef, new_off = decode_str(data, off) + off += new_off + + self.flip_damage = decode_short(data, off) + off += SHORT_OFF + + self.down_damage = decode_short(data, off) + off += SHORT_OFF + + self.poison_incidence, new_off = decode_str(data, off) + off += new_off + + self.sealed_incidence, new_off = decode_str(data, off) + off += new_off + + self.question_incidence, new_off = decode_str(data, off) + off += new_off + + self.reduce_max_hp_incidence, new_off = decode_str(data, off) + off += new_off + + self.burning_incidence, new_off = decode_str(data, off) + off += new_off + + self.quake = decode_byte(data, off) + off += BYTE_OFF + + self.can_parry = decode_byte(data, off) + off += BYTE_OFF + + self.knock_back, new_off = decode_str(data, off) + off += new_off + + self.knock_back_g, new_off = decode_str(data, off) + off += new_off + + self.forward_knock_back = decode_byte(data, off) + off += BYTE_OFF + + self.attack_type = decode_short(data, off) + off += SHORT_OFF + + self.physics_attr = decode_short(data, off) + off += SHORT_OFF + + self.magic_attr = decode_short(data, off) + off += SHORT_OFF + + self.spell_blast = decode_byte(data, off) + off += BYTE_OFF + + self.hit_se, new_off = decode_str(data, off) + off += new_off + + self.weak_se, new_off = decode_str(data, off) + off += new_off + + self.resist_se, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "MonsterAttackData": + ret = cls(b"\x00" * 99, 0) + ret.monster_attack_id = data['MonsterAttackId'] + ret.name = data['Name'] + ret.attack_coef = data['AttackCoef'] + ret.flip_damage = data['FlipDamage'] + ret.down_damage = data['DownDamage'] + ret.poison_incidence = data['PoisonIncidence'] + ret.sealed_incidence = data['SealedIncidence'] + ret.question_incidence = data['QuestionIncidence'] + ret.reduce_max_hp_incidence = data['ReduceMaxHpIncidence'] + ret.burning_incidence = data['BurningIncidence'] + ret.quake = data['Quake'] + ret.can_parry = data['CanParry'] + ret.knock_back = data['KnockBack'] + ret.knock_back_g = data['KnockBackG'] + ret.forward_knock_back = data['ForwardKnockBack'] + ret.attack_type = data['AttackType'] + ret.physics_attr = data['PhysicsAttr'] + ret.magic_attr = data['MagicAttr'] + ret.spell_blast = data['SpellBlast'] + ret.hit_se = data['HitSe'] + ret.weak_se = data['WeakSe'] + ret.resist_se = data['ResistSe'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.monster_attack_id) \ + + encode_str(self.name) \ + + encode_str(self.attack_coef) \ + + encode_short(self.flip_damage) \ + + encode_short(self.down_damage) \ + + encode_str(self.poison_incidence) \ + + encode_str(self.sealed_incidence) \ + + encode_str(self.question_incidence) \ + + encode_str(self.reduce_max_hp_incidence) \ + + encode_str(self.burning_incidence) \ + + encode_byte(self.quake) \ + + encode_byte(self.can_parry) \ + + encode_str(self.knock_back) \ + + encode_str(self.knock_back_g) \ + + encode_byte(self.forward_knock_back) \ + + encode_short(self.attack_type) \ + + encode_short(self.physics_attr) \ + + encode_short(self.magic_attr) \ + + encode_byte(self.spell_blast) \ + + encode_str(self.hit_se) \ + + encode_str(self.weak_se) \ + + encode_str(self.resist_se) + +class MonsterActionData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.monster_action_id = decode_short(data, off) + off += SHORT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self.unit_id = decode_int(data, off) + off += INT_OFF + + self.slot = decode_int(data, off) + off += INT_OFF + + self.usable_lv = decode_int(data, off) + off += INT_OFF + + self.use_count = decode_int(data, off) + off += INT_OFF + + self.not_angry = decode_byte(data, off) + off += BYTE_OFF + + self.is_angry = decode_byte(data, off) + off += BYTE_OFF + + self.can_sealed = decode_byte(data, off) + off += BYTE_OFF + + self.multi_play_only = decode_byte(data, off) + off += BYTE_OFF + + self.angle_min, new_off = decode_str(data, off) + off += new_off + + self.angle_max, new_off = decode_str(data, off) + off += new_off + + self.range_min, new_off = decode_str(data, off) + off += new_off + + self.range_max, new_off = decode_str(data, off) + off += new_off + + self.attack_time, new_off = decode_str(data, off) + off += new_off + + self.delay_time, new_off = decode_str(data, off) + off += new_off + + self.cool_time, new_off = decode_str(data, off) + off += new_off + + self.state, new_off = decode_str(data, off) + off += new_off + + self.sub_state, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "MonsterActionData": + ret = cls(b"\x00" * 99, 0) + ret.monster_action_id = data['MonsterActionId'] + ret.name = data['Name'] + ret.unit_id = data['UnitId'] + ret.slot = data['Slot'] + ret.usable_lv = data['UsableLv'] + ret.use_count = data['UseCount'] + ret.not_angry = data['NotAngry'] + ret.is_angry = data['IsAngry'] + ret.can_sealed = data['CanSealed'] + ret.multi_play_only = data['MultiPlayOnly'] + ret.angle_min = data['AngleMin'] + ret.angle_max = data['AngleMax'] + ret.range_min = data['RangeMin'] + ret.range_max = data['RangeMax'] + ret.attack_time = data['AttackTime'] + ret.delay_time = data['DelayTime'] + ret.cool_time = data['CoolTime'] + ret.state = data['State'] + ret.sub_state = data['SubState'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.monster_action_id) \ + + encode_str(self.name) \ + + encode_int(self.unit_id) \ + + encode_int(self.slot) \ + + encode_int(self.usable_lv) \ + + encode_int(self.use_count) \ + + encode_byte(self.not_angry) \ + + encode_byte(self.is_angry) \ + + encode_byte(self.can_sealed) \ + + encode_byte(self.multi_play_only) \ + + encode_str(self.angle_min) \ + + encode_str(self.angle_max) \ + + encode_str(self.range_min) \ + + encode_str(self.range_max) \ + + encode_str(self.attack_time) \ + + encode_str(self.delay_time) \ + + encode_str(self.cool_time) \ + + encode_str(self.state) \ + + encode_str(self.sub_state) + +class PropertyData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.property_id = decode_int(data, off) + off += INT_OFF + + self.property_target_type = decode_short(data, off) + off += SHORT_OFF + + self.property_name, new_off = decode_str(data, off) + off += new_off + + self.property_name_format, new_off = decode_str(data, off) + off += new_off + + self.property_type_id = decode_int(data, off) + off += INT_OFF + + self.value1_min = decode_int(data, off) + off += INT_OFF + + self.value1_max = decode_int(data, off) + off += INT_OFF + + self.value2_min = decode_int(data, off) + off += INT_OFF + + self.value2_max = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "PropertyData": + ret = cls(b"\x00" * 99, 0) + ret.property_id = data['PropertyId'] + ret.property_target_type = data['PropertyTargetType'] + ret.property_name = data['PropertyName'] + ret.property_name_format = data['PropertyNameFormat'] + ret.property_type_id = data['PropertyTypeId'] + ret.value1_min = data['Value1Min'] + ret.value1_max = data['Value1Max'] + ret.value2_min = data['Value2Min'] + ret.value2_max = data['Value2Max'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.property_id) \ + + encode_short(self.property_target_type) \ + + encode_str(self.property_name) \ + + encode_str(self.property_name_format) \ + + encode_int(self.property_type_id) \ + + encode_int(self.value1_min) \ + + encode_int(self.value1_max) \ + + encode_int(self.value2_min) \ + + encode_int(self.value2_max) + +class PropertyTableData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.property_table_id = decode_int(data, off) + off += INT_OFF + + self.property_table_sub_id = decode_int(data, off) + off += INT_OFF + + self.property_id = decode_int(data, off) + off += INT_OFF + + self.value1_min = decode_int(data, off) + off += INT_OFF + + self.value1_max = decode_int(data, off) + off += INT_OFF + + self.value2_min = decode_int(data, off) + off += INT_OFF + + self.value2_max = decode_int(data, off) + off += INT_OFF + + self.rate = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "PropertyTableData": + ret = cls(b"\x00" * 99, 0) + ret.property_table_id = data['PropertyTableId'] + ret.property_table_sub_id = data['PropertyTableSubId'] + ret.property_id = data['PropertyId'] + ret.value1_min = data['Value1Min'] + ret.value1_max = data['Value1Max'] + ret.value2_min = data['Value2Min'] + ret.value2_max = data['Value2Max'] + ret.rate = data['Rate'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.property_table_id) \ + + encode_int(self.property_table_sub_id) \ + + encode_int(self.property_id) \ + + encode_int(self.value1_min) \ + + encode_int(self.value1_max) \ + + encode_int(self.value2_min) \ + + encode_int(self.value2_max) \ + + encode_int(self.rate) + +class PropertyTypeData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.property_type_id = decode_int(data, off) + off += INT_OFF + + self.property_type_name, new_off = decode_str(data, off) + off += new_off + + self.physics_attr = decode_int(data, off) + off += INT_OFF + + self.magic_attr = decode_int(data, off) + off += INT_OFF + + self.permission_same_id_flag = decode_byte(data, off) + off += BYTE_OFF + + self.auto_equip_flag = decode_byte(data, off) + off += BYTE_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "PropertyTypeData": + ret = cls(b"\x00" * 99, 0) + ret.property_type_id = data['PropertyTypeId'] + ret.property_type_name = data['PropertyTypeName'] + ret.physics_attr = data['PhysicsAttr'] + ret.magic_attr = data['MagicAttr'] + ret.permission_same_id_flag = data['PermissionSameIdFlag'] + ret.auto_equip_flag = data['AutoEquipFlag'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.property_type_id) \ + + encode_str(self.property_type_name) \ + + encode_int(self.physics_attr) \ + + encode_int(self.magic_attr) \ + + encode_byte(self.permission_same_id_flag) \ + + encode_byte(self.auto_equip_flag) + +class SkillData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.skill_id = decode_short(data, off) + off += SHORT_OFF + + self.weapon_type_id = decode_short(data, off) + off += SHORT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self.attack = decode_byte(data, off) + off += BYTE_OFF + + self.passive = decode_byte(data, off) + off += BYTE_OFF + + self.pet = decode_byte(data, off) + off += BYTE_OFF + + self.level = decode_short(data, off) + off += SHORT_OFF + + self.skill_condition = decode_short(data, off) + off += SHORT_OFF + + self.cool_time, new_off = decode_str(data, off) + off += new_off + + self.sword_color_r, new_off = decode_str(data, off) + off += new_off + + self.sword_color_g, new_off = decode_str(data, off) + off += new_off + + self.sword_color_b, new_off = decode_str(data, off) + off += new_off + + self.motion_index = decode_short(data, off) + off += SHORT_OFF + + self.state, new_off = decode_str(data, off) + off += new_off + + self.sub_state, new_off = decode_str(data, off) + off += new_off + + self.skill_icon, new_off = decode_str(data, off) + off += new_off + + self.friend_skill_icon, new_off = decode_str(data, off) + off += new_off + + self.info_text, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "SkillData": + ret = cls(b"\x00" * 99, 0) + ret.skill_id = data['SkillId'] + ret.weapon_type_id = data['WeaponTypeId'] + ret.name = data['Name'] + ret.attack = data['Attack'] + ret.passive = data['Passive'] + ret.pet = data['Pet'] + ret.level = data['Level'] + ret.skill_condition = data['SkillCondition'] + ret.cool_time = data['CoolTime'] + ret.sword_color_r = data['SwordColorR'] + ret.sword_color_g = data['SwordColorG'] + ret.sword_color_b = data['SwordColorB'] + ret.motion_index = data['MotionIndex'] + ret.state = data['State'] + ret.sub_state = data['SubState'] + ret.skill_icon = data['SkillIcon'] + ret.friend_skill_icon = data['FriendSkillIcon'] + ret.info_text = data['InfoText'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.skill_id) \ + + encode_short(self.weapon_type_id) \ + + encode_str(self.name) \ + + encode_byte(self.attack) \ + + encode_byte(self.passive) \ + + encode_byte(self.pet) \ + + encode_short(self.level) \ + + encode_short(self.skill_condition) \ + + encode_str(self.cool_time) \ + + encode_str(self.sword_color_r) \ + + encode_str(self.sword_color_g) \ + + encode_str(self.sword_color_b) \ + + encode_short(self.motion_index) \ + + encode_str(self.state) \ + + encode_str(self.sub_state) \ + + encode_str(self.skill_icon) \ + + encode_str(self.friend_skill_icon) \ + + encode_str(self.info_text) + +class SkillTableData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.skill_table_id = decode_int(data, off) + off += INT_OFF + + self.skill_table_sub_id = decode_int(data, off) + off += INT_OFF + + self.level = decode_int(data, off) + off += INT_OFF + + self.awakening_id = decode_int(data, off) + off += INT_OFF + + self.skill_id = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "SkillTableData": + ret = cls(b"\x00" * 99, 0) + ret.skill_table_id = data['SkillTableId'] + ret.skill_table_sub_id = data['SkillTableSubId'] + ret.level = data['Level'] + ret.awakening_id = data['AwakeningId'] + ret.skill_id = data['SkillId'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.skill_table_id) \ + + encode_int(self.skill_table_sub_id) \ + + encode_int(self.level) \ + + encode_int(self.awakening_id) \ + + encode_int(self.skill_id) + +class SkillLevelData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.skill_level_id = decode_short(data, off) + off += SHORT_OFF + + self.exp = decode_int(data, off) + off += INT_OFF + + self.exp_total = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "SkillLevelData": + ret = cls(b"\x00" * 99, 0) + ret.skill_level_id = data['SkillLevelId'] + ret.exp = data['Exp'] + ret.exp_total = data['ExpTotal'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.skill_level_id) \ + + encode_int(self.exp) \ + + encode_int(self.exp_total) + +class AwakeningData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.awakening_id = decode_short(data, off) + off += SHORT_OFF + + self.total_exp = decode_int(data, off) + off += INT_OFF + + self.bonus_hero_log, new_off = decode_str(data, off) + off += new_off + + self.bonus_weapon, new_off = decode_str(data, off) + off += new_off + + self.bonus_armor, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "AwakeningData": + ret = cls(b"\x00" * 99, 0) + ret.awakening_id = data['AwakeningId'] + ret.total_exp = data['TotalExp'] + ret.bonus_hero_log = data['BonusHeroLog'] + ret.bonus_weapon = data['BonusWeapon'] + ret.bonus_armor = data['BonusArmor'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.awakening_id) \ + + encode_int(self.total_exp) \ + + encode_str(self.bonus_hero_log) \ + + encode_str(self.bonus_weapon) \ + + encode_str(self.bonus_armor) + +class SynchroSkillData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.synchro_skill_id = decode_short(data, off) + off += SHORT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self.name_in_skill_cutin, new_off = decode_str(data, off) + off += new_off + + self.sub_state, new_off = decode_str(data, off) + off += new_off + + self.chara_id = decode_short(data, off) + off += SHORT_OFF + + self.icon_file_path, new_off = decode_str(data, off) + off += new_off + + self.info_text, new_off = decode_str(data, off) + off += new_off + + self.se_name, new_off = decode_str(data, off) + off += new_off + + self.voice_id, new_off = decode_str(data, off) + off += new_off + + self.delay_time = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "SynchroSkillData": + ret = cls(b"\x00" * 99, 0) + ret.synchro_skill_id = data['SynchroSkillId'] + ret.name = data['Name'] + ret.name_in_skill_cutin = data['NameInSkillCutin'] + ret.sub_state = data['SubState'] + ret.chara_id = data['CharaId'] + ret.icon_file_path = data['IconFilePath'] + ret.info_text = data['InfoText'] + ret.se_name = data['SeName'] + ret.voice_id = data['VoiceId'] + ret.delay_time = data['DelayTime'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.synchro_skill_id) \ + + encode_str(self.name) \ + + encode_str(self.name_in_skill_cutin) \ + + encode_str(self.sub_state) \ + + encode_short(self.chara_id) \ + + encode_str(self.icon_file_path) \ + + encode_str(self.info_text) \ + + encode_str(self.se_name) \ + + encode_str(self.voice_id) \ + + encode_int(self.delay_time) + +class SoundSkillCutInVoiceData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.sound_skill_cut_in_voice_id = decode_short(data, off) + off += SHORT_OFF + + self.skill_id = decode_int(data, off) + off += INT_OFF + + self.chara_id = decode_int(data, off) + off += INT_OFF + + self.record_id, new_off = decode_str(data, off) + off += new_off + + self.que_name, new_off = decode_str(data, off) + off += new_off + + self.delay_time = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "SoundSkillCutInVoiceData": + ret = cls(b"\x00" * 99, 0) + ret.sound_skill_cut_in_voice_id = data['SoundSkillCutInVoiceId'] + ret.skill_id = data['SkillId'] + ret.chara_id = data['CharaId'] + ret.record_id = data['RecordId'] + ret.que_name = data['QueName'] + ret.delay_time = data['DelayTime'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.sound_skill_cut_in_voice_id) \ + + encode_int(self.skill_id) \ + + encode_int(self.chara_id) \ + + encode_str(self.record_id) \ + + encode_str(self.que_name) \ + + encode_int(self.delay_time) + +class QuestSceneData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.quest_scene_id = decode_short(data, off) + off += SHORT_OFF + + self.sort_no = decode_short(data, off) + off += SHORT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self.map_data, new_off = decode_str(data, off) + off += new_off + + self.unit_data, new_off = decode_str(data, off) + off += new_off + + self.demo_map, new_off = decode_str(data, off) + off += new_off + + self.bgm_basic, new_off = decode_str(data, off) + off += new_off + + self.bgm_boss, new_off = decode_str(data, off) + off += new_off + + self.tutorial = decode_byte(data, off) + off += BYTE_OFF + + self.chara_comment_id_0 = decode_int(data, off) + off += INT_OFF + + self.chara_comment_id_1 = decode_int(data, off) + off += INT_OFF + + self.chara_comment_id_2 = decode_int(data, off) + off += INT_OFF + + self.chara_comment_id_3 = decode_int(data, off) + off += INT_OFF + + self.chara_comment_id_4 = decode_int(data, off) + off += INT_OFF + + self.col_rate, new_off = decode_str(data, off) + off += new_off + + self.limit_default = decode_int(data, off) + off += INT_OFF + + self.limit_time_dec, new_off = decode_str(data, off) + off += new_off + + self.limit_chara_dec, new_off = decode_str(data, off) + off += new_off + + self.limit_resurrection = decode_int(data, off) + off += INT_OFF + + self.mission_table_sub_id = decode_int(data, off) + off += INT_OFF + + self.mission_enemy_lv_limit = decode_short(data, off) + off += SHORT_OFF + + self.reward_table_sub_id = decode_int(data, off) + off += INT_OFF + + self.player_trace_table_sub_id = decode_int(data, off) + off += INT_OFF + + self.success_player_exp = decode_int(data, off) + off += INT_OFF + + self.failed_player_exp = decode_int(data, off) + off += INT_OFF + + self.greed_spawn_wait_count = decode_int(data, off) + off += INT_OFF + + self.honey_spawn_wait_count = decode_int(data, off) + off += INT_OFF + + self.menu_display_enemy_set_id = decode_int(data, off) + off += INT_OFF + + self.stage_filepath, new_off = decode_str(data, off) + off += new_off + + self.rarity_up_chance_rate, new_off = decode_str(data, off) + off += new_off + + self.pair_mob_hp_rate, new_off = decode_str(data, off) + off += new_off + + self.pair_mob_atk_rate, new_off = decode_str(data, off) + off += new_off + + self.pair_leader_hp_rate, new_off = decode_str(data, off) + off += new_off + + self.pair_leader_atk_rate, new_off = decode_str(data, off) + off += new_off + + self.pair_boss_hp_rate, new_off = decode_str(data, off) + off += new_off + + self.pair_boss_atk_rate, new_off = decode_str(data, off) + off += new_off + + self.pair_exp_rate, new_off = decode_str(data, off) + off += new_off + + self.trio_mob_hp_rate, new_off = decode_str(data, off) + off += new_off + + self.trio_mob_atk_rate, new_off = decode_str(data, off) + off += new_off + + self.trio_leader_hp_rate, new_off = decode_str(data, off) + off += new_off + + self.trio_leader_atk_rate, new_off = decode_str(data, off) + off += new_off + + self.trio_boss_hp_rate, new_off = decode_str(data, off) + off += new_off + + self.trio_boss_atk_rate, new_off = decode_str(data, off) + off += new_off + + self.trio_exp_rate, new_off = decode_str(data, off) + off += new_off + + self.single_reward_vp = decode_int(data, off) + off += INT_OFF + + self.pair_reward_vp = decode_int(data, off) + off += INT_OFF + + self.trio_reward_vp = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "QuestSceneData": + ret = cls(b"\x00" * 99, 0) + ret.quest_scene_id = data['QuestSceneId'] + ret.sort_no = data['SortNo'] + ret.name = data['Name'] + ret.map_data = data['MapData'] + ret.unit_data = data['UnitData'] + ret.demo_map = data['DemoMap'] + ret.bgm_basic = data['BgmBasic'] + ret.bgm_boss = data['BgmBoss'] + ret.tutorial = data['Tutorial'] + ret.chara_comment_id_0 = data['CharaCommentId0'] + ret.chara_comment_id_1 = data['CharaCommentId1'] + ret.chara_comment_id_2 = data['CharaCommentId2'] + ret.chara_comment_id_3 = data['CharaCommentId3'] + ret.chara_comment_id_4 = data['CharaCommentId4'] + ret.col_rate = data['ColRate'] + ret.limit_default = data['LimitDefault'] + ret.limit_time_dec = data['LimitTimeDec'] + ret.limit_chara_dec = data['LimitCharaDec'] + ret.limit_resurrection = data['LimitResurrection'] + ret.mission_table_sub_id = data['MissionTableSubId'] + ret.mission_enemy_lv_limit = data['MissionEnemyLvLimit'] + ret.reward_table_sub_id = data['RewardTableSubId'] + ret.player_trace_table_sub_id = data['PlayerTraceTableSubId'] + ret.success_player_exp = data['SuccessPlayerExp'] + ret.failed_player_exp = data['FailedPlayerExp'] + ret.greed_spawn_wait_count = data['GreedSpawnWaitCount'] + ret.honey_spawn_wait_count = data['HoneySpawnWaitCount'] + ret.menu_display_enemy_set_id = data['MenuDisplayEnemySetId'] + ret.stage_filepath = data['StageFilepath'] + ret.rarity_up_chance_rate = data['RarityUpChanceRate'] + ret.pair_mob_hp_rate = data['PairMobHpRate'] + ret.pair_mob_atk_rate = data['PairMobAtkRate'] + ret.pair_leader_hp_rate = data['PairLeaderHpRate'] + ret.pair_leader_atk_rate = data['PairLeaderAtkRate'] + ret.pair_boss_hp_rate = data['PairBossHpRate'] + ret.pair_boss_atk_rate = data['PairBossAtkRate'] + ret.pair_exp_rate = data['PairExpRate'] + ret.trio_mob_hp_rate = data['TrioMobHpRate'] + ret.trio_mob_atk_rate = data['TrioMobAtkRate'] + ret.trio_leader_hp_rate = data['TrioLeaderHpRate'] + ret.trio_leader_atk_rate = data['TrioLeaderAtkRate'] + ret.trio_boss_hp_rate = data['TrioBossHpRate'] + ret.trio_boss_atk_rate = data['TrioBossAtkRate'] + ret.trio_exp_rate = data['TrioExpRate'] + ret.single_reward_vp = data['SingleRewardVp'] + ret.pair_reward_vp = data['PairRewardVp'] + ret.trio_reward_vp = data['TrioRewardVp'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.quest_scene_id) \ + + encode_short(self.sort_no) \ + + encode_str(self.name) \ + + encode_str(self.map_data) \ + + encode_str(self.unit_data) \ + + encode_str(self.demo_map) \ + + encode_str(self.bgm_basic) \ + + encode_str(self.bgm_boss) \ + + encode_byte(self.tutorial) \ + + encode_int(self.chara_comment_id_0) \ + + encode_int(self.chara_comment_id_1) \ + + encode_int(self.chara_comment_id_2) \ + + encode_int(self.chara_comment_id_3) \ + + encode_int(self.chara_comment_id_4) \ + + encode_str(self.col_rate) \ + + encode_int(self.limit_default) \ + + encode_str(self.limit_time_dec) \ + + encode_str(self.limit_chara_dec) \ + + encode_int(self.limit_resurrection) \ + + encode_int(self.mission_table_sub_id) \ + + encode_short(self.mission_enemy_lv_limit) \ + + encode_int(self.reward_table_sub_id) \ + + encode_int(self.player_trace_table_sub_id) \ + + encode_int(self.success_player_exp) \ + + encode_int(self.failed_player_exp) \ + + encode_int(self.greed_spawn_wait_count) \ + + encode_int(self.honey_spawn_wait_count) \ + + encode_int(self.menu_display_enemy_set_id) \ + + encode_str(self.stage_filepath) \ + + encode_str(self.rarity_up_chance_rate) \ + + encode_str(self.pair_mob_hp_rate) \ + + encode_str(self.pair_mob_atk_rate) \ + + encode_str(self.pair_leader_hp_rate) \ + + encode_str(self.pair_leader_atk_rate) \ + + encode_str(self.pair_boss_hp_rate) \ + + encode_str(self.pair_boss_atk_rate) \ + + encode_str(self.pair_exp_rate) \ + + encode_str(self.trio_mob_hp_rate) \ + + encode_str(self.trio_mob_atk_rate) \ + + encode_str(self.trio_leader_hp_rate) \ + + encode_str(self.trio_leader_atk_rate) \ + + encode_str(self.trio_boss_hp_rate) \ + + encode_str(self.trio_boss_atk_rate) \ + + encode_str(self.trio_exp_rate) \ + + encode_int(self.single_reward_vp) \ + + encode_int(self.pair_reward_vp) \ + + encode_int(self.trio_reward_vp) + +class QuestExistUnitData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.quest_exist_unit_id = decode_int(data, off) + off += INT_OFF + + self.quest_scene_id = decode_int(data, off) + off += INT_OFF + + self.enemy_kind_id = decode_int(data, off) + off += INT_OFF + + self.unit_id = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "QuestExistUnitData": + ret = cls(b"\x00" * 99, 0) + ret.quest_exist_unit_id = data['QuestExistUnitId'] + ret.quest_scene_id = data['QuestSceneId'] + ret.enemy_kind_id = data['EnemyKindId'] + ret.unit_id = data['UnitId'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.quest_exist_unit_id) \ + + encode_int(self.quest_scene_id) \ + + encode_int(self.enemy_kind_id) \ + + encode_int(self.unit_id) + +class QuestEpisodeAppendRewardData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.quest_episode_append_reward_id = decode_int(data, off) + off += INT_OFF + + self.quest_scene_id = decode_int(data, off) + off += INT_OFF + + self.episode_append_id = decode_int(data, off) + off += INT_OFF + + self.episode_append_num = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "QuestEpisodeAppendRewardData": + ret = cls(b"\x00" * 99, 0) + ret.quest_episode_append_reward_id = data['QuestEpisodeAppendRewardId'] + ret.quest_scene_id = data['QuestSceneId'] + ret.episode_append_id = data['EpisodeAppendId'] + ret.episode_append_num = data['EpisodeAppendNum'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.quest_episode_append_reward_id) \ + + encode_int(self.quest_scene_id) \ + + encode_int(self.episode_append_id) \ + + encode_int(self.episode_append_num) + +class SideQuestData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.side_quest_id = decode_int(data, off) + off += INT_OFF + + self.display_name, new_off = decode_str(data, off) + off += new_off + + self.episode_num = decode_int(data, off) + off += INT_OFF + + self.ex_bonus_table_sub_id = decode_int(data, off) + off += INT_OFF + + self.quest_scene_id = decode_int(data, off) + off += INT_OFF + + self.recommend_lv = decode_int(data, off) + off += INT_OFF + + self.comment_summary, new_off = decode_str(data, off) + off += new_off + + self.comment_introduction, new_off = decode_str(data, off) + off += new_off + + self.start_adv_name, new_off = decode_str(data, off) + off += new_off + + self.end_adv_name, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "SideQuestData": + ret = cls(b"\x00" * 99, 0) + ret.side_quest_id = data['SideQuestId'] + ret.display_name = data['DisplayName'] + ret.episode_num = data['EpisodeNum'] + ret.ex_bonus_table_sub_id = data['ExBonusTableSubId'] + ret.quest_scene_id = data['QuestSceneId'] + ret.recommend_lv = data['RecommendLv'] + ret.comment_summary = data['CommentSummary'] + ret.comment_introduction = data['CommentIntroduction'] + ret.start_adv_name = data['StartAdvName'] + ret.end_adv_name = data['EndAdvName'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.side_quest_id) \ + + encode_str(self.display_name) \ + + encode_int(self.episode_num) \ + + encode_int(self.ex_bonus_table_sub_id) \ + + encode_int(self.quest_scene_id) \ + + encode_int(self.recommend_lv) \ + + encode_str(self.comment_summary) \ + + encode_str(self.comment_introduction) \ + + encode_str(self.start_adv_name) \ + + encode_str(self.end_adv_name) + +class EpisodeData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.episode_id = decode_int(data, off) + off += INT_OFF + + self.episode_chapter_id = decode_int(data, off) + off += INT_OFF + + self.release_episode_id = decode_int(data, off) + off += INT_OFF + + self.episode_num = decode_int(data, off) + off += INT_OFF + + self.title, new_off = decode_str(data, off) + off += new_off + + self.comment_summary, new_off = decode_str(data, off) + off += new_off + + self.comment_introduction, new_off = decode_str(data, off) + off += new_off + + self.recommend_lv = decode_int(data, off) + off += INT_OFF + + self.ex_bonus_table_sub_id = decode_int(data, off) + off += INT_OFF + + self.quest_scene_id = decode_int(data, off) + off += INT_OFF + + self.start_adv_name, new_off = decode_str(data, off) + off += new_off + + self.end_adv_name, new_off = decode_str(data, off) + off += new_off + + self.unlock_still_id = decode_int(data, off) + off += INT_OFF + + self.required_release_episode_append_id = decode_int(data, off) + off += INT_OFF + + self.required_release_episode_append_num = decode_short(data, off) + off += SHORT_OFF + + self.release_reward_vp = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "EpisodeData": + ret = cls(b"\x00" * 99, 0) + ret.episode_id = data['EpisodeId'] + ret.episode_chapter_id = data['EpisodeChapterId'] + ret.release_episode_id = data['ReleaseEpisodeId'] + ret.episode_num = data['EpisodeNum'] + ret.title = data['Title'] + ret.comment_summary = data['CommentSummary'] + ret.comment_introduction = data['CommentIntroduction'] + ret.recommend_lv = data['RecommendLv'] + ret.ex_bonus_table_sub_id = data['ExBonusTableSubId'] + ret.quest_scene_id = data['QuestSceneId'] + ret.start_adv_name = data['StartAdvName'] + ret.end_adv_name = data['EndAdvName'] + ret.unlock_still_id = data['UnlockStillId'] + ret.required_release_episode_append_id = data['RequiredReleaseEpisodeAppendId'] + ret.required_release_episode_append_num = data['RequiredReleaseEpisodeAppendNum'] + ret.release_reward_vp = data['ReleaseRewardVp'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.episode_id) \ + + encode_int(self.episode_chapter_id) \ + + encode_int(self.release_episode_id) \ + + encode_int(self.episode_num) \ + + encode_str(self.title) \ + + encode_str(self.comment_summary) \ + + encode_str(self.comment_introduction) \ + + encode_int(self.recommend_lv) \ + + encode_int(self.ex_bonus_table_sub_id) \ + + encode_int(self.quest_scene_id) \ + + encode_str(self.start_adv_name) \ + + encode_str(self.end_adv_name) \ + + encode_int(self.unlock_still_id) \ + + encode_int(self.required_release_episode_append_id) \ + + encode_short(self.required_release_episode_append_num) \ + + encode_int(self.release_reward_vp) + +class EpisodeChapterData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.episode_chapter_id = decode_int(data, off) + off += INT_OFF + + self.episode_part_id = decode_int(data, off) + off += INT_OFF + + self.display_name, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "EpisodeChapterData": + ret = cls(b"\x00" * 99, 0) + ret.episode_chapter_id = data['EpisodeChapterId'] + ret.episode_part_id = data['EpisodePartId'] + ret.display_name = data['DisplayName'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.episode_chapter_id) \ + + encode_int(self.episode_part_id) \ + + encode_str(self.display_name) + +class EpisodePartData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.episode_part_id = decode_int(data, off) + off += INT_OFF + + self.display_name, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "EpisodePartData": + ret = cls(b"\x00" * 99, 0) + ret.episode_part_id = data['EpisodePartId'] + ret.display_name = data['DisplayName'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.episode_part_id) \ + + encode_str(self.display_name) + +class TrialTowerData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.trial_tower_id = decode_short(data, off) + off += SHORT_OFF + + self.release_trial_tower_id = decode_int(data, off) + off += INT_OFF + + self.ex_bonus_table_sub_id = decode_int(data, off) + off += INT_OFF + + self.quest_scene_id = decode_int(data, off) + off += INT_OFF + + self.recommend_lv = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "TrialTowerData": + ret = cls(b"\x00" * 99, 0) + ret.trial_tower_id = data['TrialTowerId'] + ret.release_trial_tower_id = data['ReleaseTrialTowerId'] + ret.ex_bonus_table_sub_id = data['ExBonusTableSubId'] + ret.quest_scene_id = data['QuestSceneId'] + ret.recommend_lv = data['RecommendLv'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.trial_tower_id) \ + + encode_int(self.release_trial_tower_id) \ + + encode_int(self.ex_bonus_table_sub_id) \ + + encode_int(self.quest_scene_id) \ + + encode_int(self.recommend_lv) + +class ExTowerData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.ex_tower_id = decode_int(data, off) + off += INT_OFF + + self.title, new_off = decode_str(data, off) + off += new_off + + self.release_trial_tower_id = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "ExTowerData": + ret = cls(b"\x00" * 99, 0) + ret.ex_tower_id = data['ExTowerId'] + ret.title = data['Title'] + ret.release_trial_tower_id = data['ReleaseTrialTowerId'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.ex_tower_id) \ + + encode_str(self.title) \ + + encode_int(self.release_trial_tower_id) + +class ExTowerQuestData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.ex_tower_quest_id = decode_int(data, off) + off += INT_OFF + + self.ex_tower_id = decode_int(data, off) + off += INT_OFF + + self.release_ex_tower_quest_id = decode_int(data, off) + off += INT_OFF + + self.level_num = decode_short(data, off) + off += SHORT_OFF + + self.title, new_off = decode_str(data, off) + off += new_off + + self.ex_bonus_table_sub_id = decode_int(data, off) + off += INT_OFF + + self.quest_scene_id = decode_int(data, off) + off += INT_OFF + + self.recommend_lv = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "ExTowerQuestData": + ret = cls(b"\x00" * 99, 0) + ret.ex_tower_quest_id = data['ExTowerQuestId'] + ret.ex_tower_id = data['ExTowerId'] + ret.release_ex_tower_quest_id = data['ReleaseExTowerQuestId'] + ret.level_num = data['LevelNum'] + ret.title = data['Title'] + ret.ex_bonus_table_sub_id = data['ExBonusTableSubId'] + ret.quest_scene_id = data['QuestSceneId'] + ret.recommend_lv = data['RecommendLv'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.ex_tower_quest_id) \ + + encode_int(self.ex_tower_id) \ + + encode_int(self.release_ex_tower_quest_id) \ + + encode_short(self.level_num) \ + + encode_str(self.title) \ + + encode_int(self.ex_bonus_table_sub_id) \ + + encode_int(self.quest_scene_id) \ + + encode_int(self.recommend_lv) + +class MenuDisplayEnemyData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.menu_display_enemy_id = decode_short(data, off) + off += SHORT_OFF + + self.menu_display_enemy_set_id = decode_int(data, off) + off += INT_OFF + + self.boss_flag = decode_byte(data, off) + off += BYTE_OFF + + self.enemy_kind_id = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "MenuDisplayEnemyData": + ret = cls(b"\x00" * 99, 0) + ret.menu_display_enemy_id = data['MenuDisplayEnemyId'] + ret.menu_display_enemy_set_id = data['MenuDisplayEnemySetId'] + ret.boss_flag = data['BossFlag'] + ret.enemy_kind_id = data['EnemyKindId'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.menu_display_enemy_id) \ + + encode_int(self.menu_display_enemy_set_id) \ + + encode_byte(self.boss_flag) \ + + encode_int(self.enemy_kind_id) + +class MissionData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.mission_id = decode_short(data, off) + off += SHORT_OFF + + self.mission_difficulty_id = decode_int(data, off) + off += INT_OFF + + self.mission_name, new_off = decode_str(data, off) + off += new_off + + self.time_limit = decode_int(data, off) + off += INT_OFF + + self.condition = decode_short(data, off) + off += SHORT_OFF + + self.enemy_set_id = decode_int(data, off) + off += INT_OFF + + self.value1 = decode_int(data, off) + off += INT_OFF + + self.value2 = decode_int(data, off) + off += INT_OFF + + self.enemy_hide = decode_byte(data, off) + off += BYTE_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "MissionData": + ret = cls(b"\x00" * 99, 0) + ret.mission_id = data['MissionId'] + ret.mission_difficulty_id = data['MissionDifficultyId'] + ret.mission_name = data['MissionName'] + ret.time_limit = data['TimeLimit'] + ret.condition = data['Condition'] + ret.enemy_set_id = data['EnemySetId'] + ret.value1 = data['Value1'] + ret.value2 = data['Value2'] + ret.enemy_hide = data['EnemyHide'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.mission_id) \ + + encode_int(self.mission_difficulty_id) \ + + encode_str(self.mission_name) \ + + encode_int(self.time_limit) \ + + encode_short(self.condition) \ + + encode_int(self.enemy_set_id) \ + + encode_int(self.value1) \ + + encode_int(self.value2) \ + + encode_byte(self.enemy_hide) + +class MissionTableData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.mission_table_id = decode_int(data, off) + off += INT_OFF + + self.mission_table_sub_id = decode_int(data, off) + off += INT_OFF + + self.mission_id = decode_int(data, off) + off += INT_OFF + + self.rate = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "MissionTableData": + ret = cls(b"\x00" * 99, 0) + ret.mission_table_id = data['MissionTableId'] + ret.mission_table_sub_id = data['MissionTableSubId'] + ret.mission_id = data['MissionId'] + ret.rate = data['Rate'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.mission_table_id) \ + + encode_int(self.mission_table_sub_id) \ + + encode_int(self.mission_id) \ + + encode_int(self.rate) + +class MissionDifficultyData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.mission_difficulty_id = decode_short(data, off) + off += SHORT_OFF + + self.skill_exp = decode_int(data, off) + off += INT_OFF + + self.unanalyzed_log_exp = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "MissionDifficultyData": + ret = cls(b"\x00" * 99, 0) + ret.mission_difficulty_id = data['MissionDifficultyId'] + ret.skill_exp = data['SkillExp'] + ret.unanalyzed_log_exp = data['UnanalyzedLogExp'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.mission_difficulty_id) \ + + encode_int(self.skill_exp) \ + + encode_int(self.unanalyzed_log_exp) + +class BattleCameraData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.battle_camera_id = decode_short(data, off) + off += SHORT_OFF + + self.offset_x, new_off = decode_str(data, off) + off += new_off + + self.offset_y, new_off = decode_str(data, off) + off += new_off + + self.offset_z, new_off = decode_str(data, off) + off += new_off + + self.rot_h, new_off = decode_str(data, off) + off += new_off + + self.rot_v, new_off = decode_str(data, off) + off += new_off + + self.distance, new_off = decode_str(data, off) + off += new_off + + self.near, new_off = decode_str(data, off) + off += new_off + + self.far, new_off = decode_str(data, off) + off += new_off + + self.fov, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "BattleCameraData": + ret = cls(b"\x00" * 99, 0) + ret.battle_camera_id = data['BattleCameraId'] + ret.offset_x = data['OffsetX'] + ret.offset_y = data['OffsetY'] + ret.offset_z = data['OffsetZ'] + ret.rot_h = data['RotH'] + ret.rot_v = data['RotV'] + ret.distance = data['Distance'] + ret.near = data['Near'] + ret.far = data['Far'] + ret.fov = data['Fov'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.battle_camera_id) \ + + encode_str(self.offset_x) \ + + encode_str(self.offset_y) \ + + encode_str(self.offset_z) \ + + encode_str(self.rot_h) \ + + encode_str(self.rot_v) \ + + encode_str(self.distance) \ + + encode_str(self.near) \ + + encode_str(self.far) \ + + encode_str(self.fov) + +class ChatMainStoryData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.chat_main_story_id = decode_int(data, off) + off += INT_OFF + + self.release_condition_type = decode_byte(data, off) + off += BYTE_OFF + + self.release_condition_value = decode_int(data, off) + off += INT_OFF + + self.display_name, new_off = decode_str(data, off) + off += new_off + + self.first_reward_vp = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "ChatMainStoryData": + ret = cls(b"\x00" * 99, 0) + ret.chat_main_story_id = data['ChatMainStoryId'] + ret.release_condition_type = data['ReleaseConditionType'] + ret.release_condition_value = data['ReleaseConditionValue'] + ret.display_name = data['DisplayName'] + ret.first_reward_vp = data['FirstRewardVp'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.chat_main_story_id) \ + + encode_byte(self.release_condition_type) \ + + encode_int(self.release_condition_value) \ + + encode_str(self.display_name) \ + + encode_int(self.first_reward_vp) + +class ChatSideStoryData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.chat_side_story_id = decode_int(data, off) + off += INT_OFF + + self.unlock_side_quest_id = decode_int(data, off) + off += INT_OFF + + self.release_condition_chara_id = decode_short(data, off) + off += SHORT_OFF + + self.release_condition_type = decode_byte(data, off) + off += BYTE_OFF + + self.release_condition_value1 = decode_int(data, off) + off += INT_OFF + + self.release_condition_value2 = decode_int(data, off) + off += INT_OFF + + self.display_name, new_off = decode_str(data, off) + off += new_off + + self.first_reward_vp = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "ChatSideStoryData": + ret = cls(b"\x00" * 99, 0) + ret.chat_side_story_id = data['ChatSideStoryId'] + ret.unlock_side_quest_id = data['UnlockSideQuestId'] + ret.release_condition_chara_id = data['ReleaseConditionCharaId'] + ret.release_condition_type = data['ReleaseConditionType'] + ret.release_condition_value1 = data['ReleaseConditionValue1'] + ret.release_condition_value2 = data['ReleaseConditionValue2'] + ret.display_name = data['DisplayName'] + ret.first_reward_vp = data['FirstRewardVp'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.chat_side_story_id) \ + + encode_int(self.unlock_side_quest_id) \ + + encode_short(self.release_condition_chara_id) \ + + encode_byte(self.release_condition_type) \ + + encode_int(self.release_condition_value1) \ + + encode_int(self.release_condition_value2) \ + + encode_str(self.display_name) \ + + encode_int(self.first_reward_vp) + +class ChatEventStoryData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.chat_event_story_id = decode_int(data, off) + off += INT_OFF + + self.event_id = decode_int(data, off) + off += INT_OFF + + self.release_condition_type = decode_byte(data, off) + off += BYTE_OFF + + self.release_condition_value = decode_int(data, off) + off += INT_OFF + + self.display_name, new_off = decode_str(data, off) + off += new_off + + self.first_reward_vp = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "ChatEventStoryData": + ret = cls(b"\x00" * 99, 0) + ret.chat_event_story_id = data['ChatEventStoryId'] + ret.event_id = data['EventId'] + ret.release_condition_type = data['ReleaseConditionType'] + ret.release_condition_value = data['ReleaseConditionValue'] + ret.display_name = data['DisplayName'] + ret.first_reward_vp = data['FirstRewardVp'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.chat_event_story_id) \ + + encode_int(self.event_id) \ + + encode_byte(self.release_condition_type) \ + + encode_int(self.release_condition_value) \ + + encode_str(self.display_name) \ + + encode_int(self.first_reward_vp) + +class NavigatorCharaData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.navigator_chara_id = decode_int(data, off) + off += INT_OFF + + self.condition_start_episode_id = decode_int(data, off) + off += INT_OFF + + self.condition_end_episode_id = decode_int(data, off) + off += INT_OFF + + self.disp_chara = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "NavigatorCharaData": + ret = cls(b"\x00" * 99, 0) + ret.navigator_chara_id = data['NavigatorCharaId'] + ret.condition_start_episode_id = data['ConditionStartEpisodeId'] + ret.condition_end_episode_id = data['ConditionEndEpisodeId'] + ret.disp_chara = data['DispChara'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.navigator_chara_id) \ + + encode_int(self.condition_start_episode_id) \ + + encode_int(self.condition_end_episode_id) \ + + encode_int(self.disp_chara) + +class NavigatorCommentData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.navigator_comment_id = decode_int(data, off) + off += INT_OFF + + self.menu_id = decode_int(data, off) + off += INT_OFF + + self.draw_type = decode_int(data, off) + off += INT_OFF + + self.condition_past_menu = decode_int(data, off) + off += INT_OFF + + self.priority = decode_int(data, off) + off += INT_OFF + + self.condition_start_episode_id = decode_int(data, off) + off += INT_OFF + + self.condition_end_episode_id = decode_int(data, off) + off += INT_OFF + + self.only_once = decode_byte(data, off) + off += BYTE_OFF + + self.only_rico = decode_byte(data, off) + off += BYTE_OFF + + self.comment_ai, new_off = decode_str(data, off) + off += new_off + + self.comment_rico, new_off = decode_str(data, off) + off += new_off + + self.expression = decode_int(data, off) + off += INT_OFF + + self.voice_id, new_off = decode_str(data, off) + off += new_off + + self.show_start_time, new_off = decode_str(data, off) + off += new_off + + self.show_end_time, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "NavigatorCommentData": + ret = cls(b"\x00" * 99, 0) + ret.navigator_comment_id = data['NavigatorCommentId'] + ret.menu_id = data['MenuId'] + ret.draw_type = data['DrawType'] + ret.condition_past_menu = data['ConditionPastMenu'] + ret.priority = data['Priority'] + ret.condition_start_episode_id = data['ConditionStartEpisodeId'] + ret.condition_end_episode_id = data['ConditionEndEpisodeId'] + ret.only_once = data['OnlyOnce'] + ret.only_rico = data['OnlyRico'] + ret.comment_ai = data['CommentAi'] + ret.comment_rico = data['CommentRico'] + ret.expression = data['Expression'] + ret.voice_id = data['VoiceId'] + ret.show_start_time = data['ShowStartTime'] + ret.show_end_time = data['ShowEndTime'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.navigator_comment_id) \ + + encode_int(self.menu_id) \ + + encode_int(self.draw_type) \ + + encode_int(self.condition_past_menu) \ + + encode_int(self.priority) \ + + encode_int(self.condition_start_episode_id) \ + + encode_int(self.condition_end_episode_id) \ + + encode_byte(self.only_once) \ + + encode_byte(self.only_rico) \ + + encode_str(self.comment_ai) \ + + encode_str(self.comment_rico) \ + + encode_int(self.expression) \ + + encode_str(self.voice_id) \ + + encode_str(self.show_start_time) \ + + encode_str(self.show_end_time) + +class ExBonusTableData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.ex_bonus_table_id = decode_int(data, off) + off += INT_OFF + + self.ex_bonus_table_sub_id = decode_int(data, off) + off += INT_OFF + + self.ex_bonus_condition_id = decode_int(data, off) + off += INT_OFF + + self.condition_value1 = decode_int(data, off) + off += INT_OFF + + self.condition_value2 = decode_int(data, off) + off += INT_OFF + + self.common_reward_type = decode_byte(data, off) + off += BYTE_OFF + + self.common_reward_id = decode_int(data, off) + off += INT_OFF + + self.common_reward_num = decode_short(data, off) + off += SHORT_OFF + + self.strength = decode_int(data, off) + off += INT_OFF + + self.property1_property_id = decode_int(data, off) + off += INT_OFF + + self.property1_value1 = decode_int(data, off) + off += INT_OFF + + self.property1_value2 = decode_int(data, off) + off += INT_OFF + + self.property2_property_id = decode_int(data, off) + off += INT_OFF + + self.property2_value1 = decode_int(data, off) + off += INT_OFF + + self.property2_value2 = decode_int(data, off) + off += INT_OFF + + self.property3_property_id = decode_int(data, off) + off += INT_OFF + + self.property3_value1 = decode_int(data, off) + off += INT_OFF + + self.property3_value2 = decode_int(data, off) + off += INT_OFF + + self.property4_property_id = decode_int(data, off) + off += INT_OFF + + self.property4_value1 = decode_int(data, off) + off += INT_OFF + + self.property4_value2 = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "ExBonusTableData": + ret = cls(b"\x00" * 99, 0) + ret.ex_bonus_table_id = data['ExBonusTableId'] + ret.ex_bonus_table_sub_id = data['ExBonusTableSubId'] + ret.ex_bonus_condition_id = data['ExBonusConditionId'] + ret.condition_value1 = data['ConditionValue1'] + ret.condition_value2 = data['ConditionValue2'] + ret.common_reward_type = data['CommonRewardType'] + ret.common_reward_id = data['CommonRewardId'] + ret.common_reward_num = data['CommonRewardNum'] + ret.strength = data['Strength'] + ret.property1_property_id = data['Property1PropertyId'] + ret.property1_value1 = data['Property1Value1'] + ret.property1_value2 = data['Property1Value2'] + ret.property2_property_id = data['Property2PropertyId'] + ret.property2_value1 = data['Property2Value1'] + ret.property2_value2 = data['Property2Value2'] + ret.property3_property_id = data['Property3PropertyId'] + ret.property3_value1 = data['Property3Value1'] + ret.property3_value2 = data['Property3Value2'] + ret.property4_property_id = data['Property4PropertyId'] + ret.property4_value1 = data['Property4Value1'] + ret.property4_value2 = data['Property4Value2'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.ex_bonus_table_id) \ + + encode_int(self.ex_bonus_table_sub_id) \ + + encode_int(self.ex_bonus_condition_id) \ + + encode_int(self.condition_value1) \ + + encode_int(self.condition_value2) \ + + encode_byte(self.common_reward_type) \ + + encode_int(self.common_reward_id) \ + + encode_short(self.common_reward_num) \ + + encode_int(self.strength) \ + + encode_int(self.property1_property_id) \ + + encode_int(self.property1_value1) \ + + encode_int(self.property1_value2) \ + + encode_int(self.property2_property_id) \ + + encode_int(self.property2_value1) \ + + encode_int(self.property2_value2) \ + + encode_int(self.property3_property_id) \ + + encode_int(self.property3_value1) \ + + encode_int(self.property3_value2) \ + + encode_int(self.property4_property_id) \ + + encode_int(self.property4_value1) \ + + encode_int(self.property4_value2) + +class ExBonusConditionData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.ex_bonus_condition_id = decode_int(data, off) + off += INT_OFF + + self.format, new_off = decode_str(data, off) + off += new_off + + self.hud_format, new_off = decode_str(data, off) + off += new_off + + self.format_param_size = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "ExBonusConditionData": + ret = cls(b"\x00" * 99, 0) + ret.ex_bonus_condition_id = data['ExBonusConditionId'] + ret.format = data['Format'] + ret.hud_format = data['HudFormat'] + ret.format_param_size = data['FomatParamSize'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.ex_bonus_condition_id) \ + + encode_str(self.format) \ + + encode_str(self.hud_format) \ + + encode_int(self.format_param_size) + +class BeginnerMissionProgressesUserData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.beginner_mission_condition_id = decode_int(data, off) + off += INT_OFF + + self.achievement_num = decode_short(data, off) + off += SHORT_OFF + + self.complete_flag = decode_byte(data, off) + off += BYTE_OFF + + self.complete_date, new_off = decode_date_str(data, off) + off += new_off + + self.reward_received_flag = decode_byte(data, off) + off += BYTE_OFF + + self.reward_received_date, new_off = decode_date_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "BeginnerMissionProgressesUserData": + ret = cls(b"\x00" * 99, 0) + ret.beginner_mission_condition_id = data['BeginnerMissionConditionId'] + ret.achievement_num = data['AchievementNum'] + ret.complete_flag = data['CompleteFlag'] + ret.complete_date = data['CompleteDate'] + ret.reward_received_flag = data['RewardReceivedFlag'] + ret.reward_received_date = data['RewardReceivedDate'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.beginner_mission_condition_id) \ + + encode_short(self.achievement_num) \ + + encode_byte(self.complete_flag) \ + + encode_date_str(self.complete_date) \ + + encode_byte(self.reward_received_flag) \ + + encode_date_str(self.reward_received_date) + +class BeginnerMissionSeatProgressesUserData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.beginner_mission_seat_condition_id = decode_int(data, off) + off += INT_OFF + + self.achievement_num = decode_short(data, off) + off += SHORT_OFF + + self.complete_flag = decode_byte(data, off) + off += BYTE_OFF + + self.complete_date, new_off = decode_date_str(data, off) + off += new_off + + self.reward_received_flag = decode_byte(data, off) + off += BYTE_OFF + + self.reward_received_date, new_off = decode_date_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "BeginnerMissionSeatProgressesUserData": + ret = cls(b"\x00" * 99, 0) + ret.beginner_mission_seat_condition_id = data['BeginnerMissionSeatConditionId'] + ret.achievement_num = data['AchievementNum'] + ret.complete_flag = data['CompleteFlag'] + ret.complete_date = data['CompleteDate'] + ret.reward_received_flag = data['RewardReceivedFlag'] + ret.reward_received_date = data['RewardReceivedDate'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.beginner_mission_seat_condition_id) \ + + encode_short(self.achievement_num) \ + + encode_byte(self.complete_flag) \ + + encode_date_str(self.complete_date) \ + + encode_byte(self.reward_received_flag) \ + + encode_date_str(self.reward_received_date) + +class LinkedSiteRegCampaignUserData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.reward_received_flag = decode_byte(data, off) + off += BYTE_OFF + + self.reward_received_date, new_off = decode_date_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "LinkedSiteRegCampaignUserData": + ret = cls(b"\x00" * 99, 0) + ret.reward_received_flag = data['RewardReceivedFlag'] + ret.reward_received_date = data['RewardReceivedDate'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.reward_received_flag) \ + + encode_date_str(self.reward_received_date) + +class HeroLogUnitUserData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.hero_log_id = decode_int(data, off) + off += INT_OFF + + self.leader_appointment_normal_card_num = decode_int(data, off) + off += INT_OFF + + self.leader_appointment_holographic_card_num = decode_int(data, off) + off += INT_OFF + + self.skill_slot_max_release_flag = decode_byte(data, off) + off += BYTE_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "HeroLogUnitUserData": + ret = cls(b"\x00" * 13, 0) + ret.hero_log_id = data['HeroLogId'] + ret.leader_appointment_normal_card_num = data['LeaderAppointmentNormalCardNum'] + ret.leader_appointment_holographic_card_num = data['LeaderAppointmentHolographicCardNum'] + ret.skill_slot_max_release_flag = data['SkillSlotMaxReleaseFlag'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.hero_log_id) \ + + encode_int(self.leader_appointment_normal_card_num) \ + + encode_int(self.leader_appointment_holographic_card_num) \ + + encode_byte(self.skill_slot_max_release_flag) + +class CharaUnitUserData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.chara_id = decode_short(data, off) + off += SHORT_OFF + + self.reliability_1 = decode_int(data, off) + off += INT_OFF + + self.reliability_2 = decode_int(data, off) + off += INT_OFF + + self.motivation_flag = decode_byte(data, off) + off += BYTE_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "CharaUnitUserData": + ret = cls(b"\x00" * 99, 0) + ret.chara_id = data['CharaId'] + ret.reliability_1 = data['Reliability1'] + ret.reliability_2 = data['Reliability2'] + ret.motivation_flag = data['MotivationFlag'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.chara_id) \ + + encode_int(self.reliability_1) \ + + encode_int(self.reliability_2) \ + + encode_byte(self.motivation_flag) + +class AdventureExecUserData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.status = decode_byte(data, off) + off += BYTE_OFF + + self.adventure_id = decode_int(data, off) + off += INT_OFF + + self.adventure_difficulty_id = decode_int(data, off) + off += INT_OFF + + self.last_start_date, new_off = decode_date_str(data, off) + off += new_off + + self.remaining_sec_to_complete = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "AdventureExecUserData": + ret = cls(b"\x00" * 99, 0) + ret.status = data['Status'] + ret.adventure_id = data['AdventureId'] + ret.adventure_difficulty_id = data['AdventureDifficultyId'] + ret.last_start_date = data['LastStartDate'] + ret.remaining_sec_to_complete = data['RemainingSecToComplete'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.status) \ + + encode_int(self.adventure_id) \ + + encode_int(self.adventure_difficulty_id) \ + + encode_date_str(self.last_start_date) \ + + encode_int(self.remaining_sec_to_complete) + +class QuestRareDropData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.quest_rare_drop_id = decode_int(data, off) + off += INT_OFF + + self.quest_scene_id = decode_int(data, off) + off += INT_OFF + + self.unit_id = decode_int(data, off) + off += INT_OFF + + self.common_reward_type = decode_int(data, off) + off += INT_OFF + + self.common_reward_id = decode_int(data, off) + off += INT_OFF + + self.common_reward_num = decode_int(data, off) + off += INT_OFF + + self.strength_min = decode_int(data, off) + off += INT_OFF + + self.strength_max = decode_int(data, off) + off += INT_OFF + + self.property_table_sub_id = decode_int(data, off) + off += INT_OFF + + self.drop_rate, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "QuestRareDropData": + ret = cls(b"\x00" * 99, 0) + ret.quest_rare_drop_id = data['QuestRareDropId'] + ret.quest_scene_id = data['QuestSceneId'] + ret.unit_id = data['UnitId'] + ret.common_reward_type = data['CommonRewardType'] + ret.common_reward_id = data['CommonRewardId'] + ret.common_reward_num = data['CommonRewardNum'] + ret.strength_min = data['StrengthMin'] + ret.strength_max = data['StrengthMax'] + ret.property_table_sub_id = data['PropertyTableSubId'] + ret.drop_rate = data['DropRate'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.quest_rare_drop_id) \ + + encode_int(self.quest_scene_id) \ + + encode_int(self.unit_id) \ + + encode_int(self.common_reward_type) \ + + encode_int(self.common_reward_id) \ + + encode_int(self.common_reward_num) \ + + encode_int(self.strength_min) \ + + encode_int(self.strength_max) \ + + encode_int(self.property_table_sub_id) \ + + encode_str(self.drop_rate) + +class QuestSpecialRareDropSettingData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.quest_special_rare_drop_setting_id = decode_int(data, off) + off += INT_OFF + + self.tower_type = decode_byte(data, off) + off += BYTE_OFF + + self.start_trial_tower_id = decode_short(data, off) + off += SHORT_OFF + + self.end_trial_tower_id = decode_short(data, off) + off += SHORT_OFF + + self.rare_drop_upward_rate, new_off = decode_str(data, off) + off += new_off + + self.quest_special_rare_drop_sub_id = decode_int(data, off) + off += INT_OFF + + self.generic_campaign_period_id = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "QuestSpecialRareDropSettingData": + ret = cls(b"\x00" * 99, 0) + ret.quest_special_rare_drop_setting_id = data['QuestSpecialRareDropSettingId'] + ret.tower_type = data['TowerType'] + ret.start_trial_tower_id = data['StartTrialTowerId'] + ret.end_trial_tower_id = data['EndTrialTowerId'] + ret.rare_drop_upward_rate = data['RareDropUpwardRate'] + ret.quest_special_rare_drop_sub_id = data['QuestSpecialRareDropSubId'] + ret.generic_campaign_period_id = data['GenericCampaignPeriodId'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.quest_special_rare_drop_setting_id) \ + + encode_byte(self.tower_type) \ + + encode_short(self.start_trial_tower_id) \ + + encode_short(self.end_trial_tower_id) \ + + encode_str(self.rare_drop_upward_rate) \ + + encode_int(self.quest_special_rare_drop_sub_id) \ + + encode_int(self.generic_campaign_period_id) + +class QuestSpecialRareDropData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.quest_special_rare_drop_id = decode_int(data, off) + off += INT_OFF + + self.quest_special_rare_drop_sub_id = decode_int(data, off) + off += INT_OFF + + self.unit_id = decode_int(data, off) + off += INT_OFF + + self.common_reward_type = decode_int(data, off) + off += INT_OFF + + self.common_reward_id = decode_int(data, off) + off += INT_OFF + + self.common_reward_num = decode_int(data, off) + off += INT_OFF + + self.strength_min = decode_int(data, off) + off += INT_OFF + + self.strength_max = decode_int(data, off) + off += INT_OFF + + self.property_table_sub_id = decode_int(data, off) + off += INT_OFF + + self.drop_rate, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "QuestSpecialRareDropData": + ret = cls(b"\x00" * 99, 0) + ret.quest_special_rare_drop_id = data['QuestSpecialRareDropId'] + ret.quest_special_rare_drop_sub_id = data['QuestSpecialRareDropSubId'] + ret.unit_id = data['UnitId'] + ret.common_reward_type = data['CommonRewardType'] + ret.common_reward_id = data['CommonRewardId'] + ret.common_reward_num = data['CommonRewardNum'] + ret.strength_min = data['StrengthMin'] + ret.strength_max = data['StrengthMax'] + ret.property_table_sub_id = data['PropertyTableSubId'] + ret.drop_rate = data['DropRate'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.quest_special_rare_drop_id) \ + + encode_int(self.quest_special_rare_drop_sub_id) \ + + encode_int(self.unit_id) \ + + encode_int(self.common_reward_type) \ + + encode_int(self.common_reward_id) \ + + encode_int(self.common_reward_num) \ + + encode_int(self.strength_min) \ + + encode_int(self.strength_max) \ + + encode_int(self.property_table_sub_id) \ + + encode_str(self.drop_rate) + +class QuestTutorialData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.quest_tutorial_id = decode_short(data, off) + off += SHORT_OFF + + self.trigger = decode_int(data, off) + off += INT_OFF + + self.trigger_delay, new_off = decode_str(data, off) + off += new_off + + self.trigger_during, new_off = decode_str(data, off) + off += new_off + + self.unit_id = decode_int(data, off) + off += INT_OFF + + self.unit_range, new_off = decode_str(data, off) + off += new_off + + self.comment_text, new_off = decode_str(data, off) + off += new_off + + self.voice_id, new_off = decode_str(data, off) + off += new_off + + self.comment_pos, new_off = decode_str(data, off) + off += new_off + + self.yui_face, new_off = decode_str(data, off) + off += new_off + + self.arrow_direct, new_off = decode_str(data, off) + off += new_off + + self.arrow_position, new_off = decode_str(data, off) + off += new_off + + self.image_plus, new_off = decode_str(data, off) + off += new_off + + self.image_plus_pos, new_off = decode_str(data, off) + off += new_off + + self.stick_info, new_off = decode_str(data, off) + off += new_off + + self.mark_effect, new_off = decode_str(data, off) + off += new_off + + self.mark_pos, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "QuestTutorialData": + ret = cls(b"\x00" * 99, 0) + ret.quest_tutorial_id = data['Id'] + ret.trigger = data['Trigger'] + ret.trigger_delay = data['TriggerDelay'] + ret.trigger_during = data['TriggerDuring'] + ret.unit_id = data['UnitId'] + ret.unit_range = data['UnitRange'] + ret.comment_text = data['CommentText'] + ret.voice_id = data['VoiceID'] + ret.comment_pos = data['CommentPos'] + ret.yui_face = data['YuiFace'] + ret.arrow_direct = data['ArrowDirect'] + ret.arrow_position = data['ArrowPosition'] + ret.image_plus = data['ImagePlus'] + ret.image_plus_pos = data['ImagePlusPos'] + ret.stick_info = data['StickInfo'] + ret.mark_effect = data['MarkEffect'] + ret.mark_pos = data['MarkPos'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.quest_tutorial_id) \ + + encode_int(self.trigger) \ + + encode_str(self.trigger_delay) \ + + encode_str(self.trigger_during) \ + + encode_int(self.unit_id) \ + + encode_str(self.unit_range) \ + + encode_str(self.comment_text) \ + + encode_str(self.voice_id) \ + + encode_str(self.comment_pos) \ + + encode_str(self.yui_face) \ + + encode_str(self.arrow_direct) \ + + encode_str(self.arrow_position) \ + + encode_str(self.image_plus) \ + + encode_str(self.image_plus_pos) \ + + encode_str(self.stick_info) \ + + encode_str(self.mark_effect) \ + + encode_str(self.mark_pos) + +class QuestPlayerTraceTableData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.player_trace_table_id = decode_short(data, off) + off += SHORT_OFF + + self.player_trace_table_sub_id = decode_int(data, off) + off += INT_OFF + + self.common_reward_type = decode_byte(data, off) + off += BYTE_OFF + + self.common_reward_id = decode_int(data, off) + off += INT_OFF + + self.common_reward_num = decode_short(data, off) + off += SHORT_OFF + + self.rate = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "QuestPlayerTraceTableData": + ret = cls(b"\x00" * 99, 0) + ret.player_trace_table_id = data['PlayerTraceTableId'] + ret.player_trace_table_sub_id = data['PlayerTraceTableSubId'] + ret.common_reward_type = data['CommonRewardType'] + ret.common_reward_id = data['CommonRewardId'] + ret.common_reward_num = data['CommonRewardNum'] + ret.rate = data['Rate'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.player_trace_table_id) \ + + encode_int(self.player_trace_table_sub_id) \ + + encode_byte(self.common_reward_type) \ + + encode_int(self.common_reward_id) \ + + encode_short(self.common_reward_num) \ + + encode_int(self.rate) + +class QuestStillData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.still_id = decode_int(data, off) + off += INT_OFF + + self.file_name, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "QuestStillData": + ret = cls(b"\x00" * 99, 0) + ret.still_id = data['StillId'] + ret.file_name = data['FileName'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.still_id) \ + + encode_str(self.file_name) + +class GashaData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.gasha_id = decode_int(data, off) + off += INT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self.description, new_off = decode_str(data, off) + off += new_off + + self.gasha_type = decode_byte(data, off) + off += BYTE_OFF + + self.open_type = decode_byte(data, off) + off += BYTE_OFF + + self.free_target_flag = decode_byte(data, off) + off += BYTE_OFF + + self.reset_hour_list, new_off = decode_str(data, off) + off += new_off + + self.limit_num = decode_short(data, off) + off += SHORT_OFF + + self.open_days = decode_short(data, off) + off += SHORT_OFF + + self.sort_num = decode_int(data, off) + off += INT_OFF + + self.start_date, new_off = decode_date_str(data, off) + off += new_off + + self.end_date, new_off = decode_date_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "GashaData": + ret = cls(b"\x00" * 99, 0) + ret.gasha_id = data['GashaId'] + ret.name = data['Name'] + ret.description = data['Description'] + ret.gasha_type = data['GashaType'] + ret.open_type = data['OpenType'] + ret.free_target_flag = data['FreeTargetFlag'] + ret.reset_hour_list = data['ResetHourList'] + ret.limit_num = data['LimitNum'] + ret.open_days = data['OpenDays'] + ret.sort_num = data['SortNum'] + ret.start_date = data['StartDate'] + ret.end_date = data['EndDate'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.gasha_id) \ + + encode_str(self.name) \ + + encode_str(self.description) \ + + encode_byte(self.gasha_type) \ + + encode_byte(self.open_type) \ + + encode_byte(self.free_target_flag) \ + + encode_str(self.reset_hour_list) \ + + encode_short(self.limit_num) \ + + encode_short(self.open_days) \ + + encode_int(self.sort_num) \ + + encode_date_str(self.start_date) \ + + encode_date_str(self.end_date) + +class GashaHeaderData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.gasha_header_id = decode_int(data, off) + off += INT_OFF + + self.gasha_id = decode_int(data, off) + off += INT_OFF + + self.step_no = decode_byte(data, off) + off += BYTE_OFF + + self.step_progress_flag = decode_byte(data, off) + off += BYTE_OFF + + self.platform = decode_byte(data, off) + off += BYTE_OFF + + self.use_type_1 = decode_byte(data, off) + off += BYTE_OFF + + self.use_type_2 = decode_byte(data, off) + off += BYTE_OFF + + self.provision_num = decode_byte(data, off) + off += BYTE_OFF + + self.premium_lottery_num = decode_byte(data, off) + off += BYTE_OFF + + self.premium_lottery_executable_num = decode_byte(data, off) + off += BYTE_OFF + + self.use_type_1_first_use_num = decode_int(data, off) + off += INT_OFF + + self.use_type_1_base_use_num = decode_int(data, off) + off += INT_OFF + + self.use_type_1_extra_use_num = decode_int(data, off) + off += INT_OFF + + self.use_type_2_first_use_num = decode_int(data, off) + off += INT_OFF + + self.use_type_2_base_use_num = decode_int(data, off) + off += INT_OFF + + self.use_type_2_extra_use_num = decode_int(data, off) + off += INT_OFF + + self.yui_chance_log_open_rate, new_off = decode_str(data, off) + off += new_off + + self.yui_chance_rarity_up_rate, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "GashaHeaderData": + ret = cls(b"\x00" * 99, 0) + ret.gasha_header_id = data['GashaHeaderId'] + ret.gasha_id = data['GashaId'] + ret.step_no = data['StepNo'] + ret.step_progress_flag = data['StepProgressFlag'] + ret.platform = data['Platform'] + ret.use_type_1 = data['UseType1'] + ret.use_type_2 = data['UseType2'] + ret.provision_num = data['ProvisionNum'] + ret.premium_lottery_num = data['PremiumLotteryNum'] + ret.premium_lottery_executable_num = data['PremiumLotteryExecutableNum'] + ret.use_type_1_first_use_num = data['UseType1FirstUseNum'] + ret.use_type_1_base_use_num = data['UseType1BaseUseNum'] + ret.use_type_1_extra_use_num = data['UseType1ExtraUseNum'] + ret.use_type_2_first_use_num = data['UseType2FirstUseNum'] + ret.use_type_2_base_use_num = data['UseType2BaseUseNum'] + ret.use_type_2_extra_use_num = data['UseType2ExtraUseNum'] + ret.yui_chance_log_open_rate = data['YuiChanceLogOpenRate'] + ret.yui_chance_rarity_up_rate = data['YuiChanceRarityUpRate'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.gasha_header_id) \ + + encode_int(self.gasha_id) \ + + encode_byte(self.step_no) \ + + encode_byte(self.step_progress_flag) \ + + encode_byte(self.platform) \ + + encode_byte(self.use_type_1) \ + + encode_byte(self.use_type_2) \ + + encode_byte(self.provision_num) \ + + encode_byte(self.premium_lottery_num) \ + + encode_byte(self.premium_lottery_executable_num) \ + + encode_int(self.use_type_1_first_use_num) \ + + encode_int(self.use_type_1_base_use_num) \ + + encode_int(self.use_type_1_extra_use_num) \ + + encode_int(self.use_type_2_first_use_num) \ + + encode_int(self.use_type_2_base_use_num) \ + + encode_int(self.use_type_2_extra_use_num) \ + + encode_str(self.yui_chance_log_open_rate) \ + + encode_str(self.yui_chance_rarity_up_rate) + +class GashaLotteryRarityData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.gasha_lottery_rarity_id = decode_int(data, off) + off += INT_OFF + + self.gasha_id = decode_int(data, off) + off += INT_OFF + + self.gasha_header_id = decode_int(data, off) + off += INT_OFF + + self.lottery_rarity_type = decode_byte(data, off) + off += BYTE_OFF + + self.lottery_rarity_rate, new_off = decode_str(data, off) + off += new_off + + self.premium_rate, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "GashaLotteryRarityData": + ret = cls(b"\x00" * 99, 0) + ret.gasha_lottery_rarity_id = data['GashaLotteryRarityId'] + ret.gasha_id = data['GashaId'] + ret.gasha_header_id = data['GashaHeaderId'] + ret.lottery_rarity_type = data['LotteryRarityType'] + ret.lottery_rarity_rate = data['LotteryRarityRate'] + ret.premium_rate = data['PremiumRate'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.gasha_lottery_rarity_id) \ + + encode_int(self.gasha_id) \ + + encode_int(self.gasha_header_id) \ + + encode_byte(self.lottery_rarity_type) \ + + encode_str(self.lottery_rarity_rate) \ + + encode_str(self.premium_rate) + +class GashaPrizeData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.gasha_prize_id = decode_int(data, off) + off += INT_OFF + + self.gasha_id = decode_int(data, off) + off += INT_OFF + + self.lottery_rarity_type = decode_byte(data, off) + off += BYTE_OFF + + self.common_reward_type = decode_int(data, off) + off += INT_OFF + + self.common_reward_id = decode_int(data, off) + off += INT_OFF + + self.common_reward_num = decode_int(data, off) + off += INT_OFF + + self.unanalyzed_log_grade_id = decode_int(data, off) + off += INT_OFF + + self.correction_rate = decode_short(data, off) + off += SHORT_OFF + + self.pickup_flag = decode_byte(data, off) + off += BYTE_OFF + + self.property_table_sub_id = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "GashaPrizeData": + ret = cls(b"\x00" * 99, 0) + ret.gasha_prize_id = data['GashaPrizeId'] + ret.gasha_id = data['GashaId'] + ret.lottery_rarity_type = data['LotteryRarityType'] + ret.common_reward_type = data['CommonRewardType'] + ret.common_reward_id = data['CommonRewardId'] + ret.common_reward_num = data['CommonRewardNum'] + ret.unanalyzed_log_grade_id = data['UnanalyzedLogGradeId'] + ret.correction_rate = data['CorrectionRate'] + ret.pickup_flag = data['PickupFlag'] + ret.property_table_sub_id = data['PropertyTableSubId'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.gasha_prize_id) \ + + encode_int(self.gasha_id) \ + + encode_byte(self.lottery_rarity_type) \ + + encode_int(self.common_reward_type) \ + + encode_int(self.common_reward_id) \ + + encode_int(self.common_reward_num) \ + + encode_int(self.unanalyzed_log_grade_id) \ + + encode_short(self.correction_rate) \ + + encode_byte(self.pickup_flag) \ + + encode_int(self.property_table_sub_id) + +class ComebackEventData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.comeback_event_id = decode_int(data, off) + off += INT_OFF + + self.comeback_event_sub_id = decode_int(data, off) + off += INT_OFF + + self.display_name, new_off = decode_str(data, off) + off += new_off + + self.reward_set_sub_id = decode_int(data, off) + off += INT_OFF + + self.require_days = decode_short(data, off) + off += SHORT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "ComebackEventData": + ret = cls(b"\x00" * 99, 0) + ret.comeback_event_id = data['ComebackEventId'] + ret.comeback_event_sub_id = data['ComebackEventSubId'] + ret.display_name = data['DisplayName'] + ret.reward_set_sub_id = data['RewardSetSubId'] + ret.require_days = data['RequireDays'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.comeback_event_id) \ + + encode_int(self.comeback_event_sub_id) \ + + encode_str(self.display_name) \ + + encode_int(self.reward_set_sub_id) \ + + encode_short(self.require_days) + +class AdBannerData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.ad_banner_id = decode_int(data, off) + off += INT_OFF + + self.category = decode_byte(data, off) + off += BYTE_OFF + + self.sort_num = decode_int(data, off) + off += INT_OFF + + self.start_date, new_off = decode_date_str(data, off) + off += new_off + + self.end_date, new_off = decode_date_str(data, off) + off += new_off + + self.active_flag = decode_byte(data, off) + off += BYTE_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "AdBannerData": + ret = cls(b"\x00" * 99, 0) + ret.ad_banner_id = data['AdBannerId'] + ret.category = data['Category'] + ret.sort_num = data['SortNum'] + ret.start_date = data['StartDate'] + ret.end_date = data['EndDate'] + ret.active_flag = data['ActiveFlag'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.ad_banner_id) \ + + encode_byte(self.category) \ + + encode_int(self.sort_num) \ + + encode_date_str(self.start_date) \ + + encode_date_str(self.end_date) \ + + encode_byte(self.active_flag) + +class EventsData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.event_id = decode_int(data, off) + off += INT_OFF + + self.event_type = decode_byte(data, off) + off += BYTE_OFF + + self.title, new_off = decode_str(data, off) + off += new_off + + self.param_1, new_off = decode_str(data, off) + off += new_off + + self.param_2, new_off = decode_str(data, off) + off += new_off + + self.param_3, new_off = decode_str(data, off) + off += new_off + + self.open_start_date, new_off = decode_date_str(data, off) + off += new_off + + self.open_end_date, new_off = decode_date_str(data, off) + off += new_off + + self.posting_start_date, new_off = decode_date_str(data, off) + off += new_off + + self.posting_end_date, new_off = decode_date_str(data, off) + off += new_off + + self.chat_open_start_date, new_off = decode_date_str(data, off) + off += new_off + + self.chat_open_end_date, new_off = decode_date_str(data, off) + off += new_off + + self.news_id = decode_int(data, off) + off += INT_OFF + + self.help_id = decode_int(data, off) + off += INT_OFF + + self.ad_banner_id = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "EventsData": + ret = cls(b"\x00" * 99, 0) + ret.event_id = data['EventId'] + ret.event_type = data['EventType'] + ret.title = data['Title'] + ret.param_1 = data['Param1'] + ret.param_2 = data['Param2'] + ret.param_3 = data['Param3'] + ret.open_start_date = data['OpenStartDate'] + ret.open_end_date = data['OpenEndDate'] + ret.posting_start_date = data['PostingStartDate'] + ret.posting_end_date = data['PostingEndDate'] + ret.chat_open_start_date = data['ChatOpenStartDate'] + ret.chat_open_end_date = data['ChatOpenEndDate'] + ret.news_id = data['NewId'] + ret.help_id = data['HelpId'] + ret.ad_banner_id = data['AdBannerId'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.event_id) \ + + encode_byte(self.event_type) \ + + encode_str(self.title) \ + + encode_str(self.param_1) \ + + encode_str(self.param_2) \ + + encode_str(self.param_3) \ + + encode_date_str(self.open_start_date) \ + + encode_date_str(self.open_end_date) \ + + encode_date_str(self.posting_start_date) \ + + encode_date_str(self.posting_end_date) \ + + encode_date_str(self.chat_open_start_date) \ + + encode_date_str(self.chat_open_end_date) \ + + encode_int(self.news_id) \ + + encode_int(self.help_id) \ + + encode_int(self.ad_banner_id) + +class TreasureHuntsData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.treasure_hunt_id = decode_int(data, off) + off += INT_OFF + + self.title, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "TreasureHuntsData": + ret = cls(b"\x00" * 99, 0) + ret.treasure_hunt_id = data['TreasureHuntId'] + ret.title = data['Title'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.treasure_hunt_id) \ + + encode_str(self.title) + +class TreasureHuntWholeTasksData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.treasure_hunt_whole_task_id = decode_int(data, off) + off += INT_OFF + + self.treasure_hunt_id = decode_int(data, off) + off += INT_OFF + + self.task_type = decode_byte(data, off) + off += BYTE_OFF + + self.achievement_possible_num = decode_short(data, off) + off += SHORT_OFF + + self.get_event_point = decode_int(data, off) + off += INT_OFF + + self.condition_type = decode_byte(data, off) + off += BYTE_OFF + + self.condition_value_accumulation_flag = decode_byte(data, off) + off += BYTE_OFF + + self.condition_value_1, new_off = decode_str(data, off) + off += new_off + + self.condition_value_2, new_off = decode_str(data, off) + off += new_off + + self.condition_value_3, new_off = decode_str(data, off) + off += new_off + + self.condition_value_4, new_off = decode_str(data, off) + off += new_off + + self.condition_value_5, new_off = decode_str(data, off) + off += new_off + + self.open_start_date, new_off = decode_date_str(data, off) + off += new_off + + self.open_end_date, new_off = decode_date_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "TreasureHuntWholeTasksData": + ret = cls(b"\x00" * 99, 0) + ret.treasure_hunt_whole_task_id = data['TreasureHuntWholeTaskId'] + ret.treasure_hunt_id = data['TreasureHuntId'] + ret.task_type = data['TaskType'] + ret.achievement_possible_num = data['AchievementPossibleNum'] + ret.get_event_point = data['GetEventPoint'] + ret.condition_type = data['ConditionType'] + ret.condition_value_accumulation_flag = data['ConditionValueAccumulationFlag'] + ret.condition_value_1 = data['ConditionValue1'] + ret.condition_value_2 = data['ConditionValue2'] + ret.condition_value_3 = data['ConditionValue3'] + ret.condition_value_4 = data['ConditionValue4'] + ret.condition_value_5 = data['ConditionValue5'] + ret.open_start_date = data['OpenStartDate'] + ret.open_end_date = data['OpenEndDate'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.treasure_hunt_whole_task_id) \ + + encode_int(self.treasure_hunt_id) \ + + encode_byte(self.task_type) \ + + encode_short(self.achievement_possible_num) \ + + encode_int(self.get_event_point) \ + + encode_byte(self.condition_type) \ + + encode_byte(self.condition_value_accumulation_flag) \ + + encode_str(self.condition_value_1) \ + + encode_str(self.condition_value_2) \ + + encode_str(self.condition_value_3) \ + + encode_str(self.condition_value_4) \ + + encode_str(self.condition_value_5) \ + + encode_date_str(self.open_start_date) \ + + encode_date_str(self.open_end_date) + +class TreasureHuntIndividualTasksData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.treasure_hunt_individual_task_id = decode_int(data, off) + off += INT_OFF + + self.treasure_hunt_id = decode_int(data, off) + off += INT_OFF + + self.task_type = decode_byte(data, off) + off += BYTE_OFF + + self.quest_scene_id = decode_short(data, off) + off += SHORT_OFF + + self.achievement_possible_num = decode_short(data, off) + off += SHORT_OFF + + self.get_event_point = decode_int(data, off) + off += INT_OFF + + self.condition_type = decode_byte(data, off) + off += BYTE_OFF + + self.condition_value_accumulation_flag = decode_byte(data, off) + off += BYTE_OFF + + self.condition_value_1, new_off = decode_str(data, off) + off += new_off + + self.condition_value_2, new_off = decode_str(data, off) + off += new_off + + self.condition_value_3, new_off = decode_str(data, off) + off += new_off + + self.condition_value_4, new_off = decode_str(data, off) + off += new_off + + self.condition_value_5, new_off = decode_str(data, off) + off += new_off + + self.open_start_date, new_off = decode_date_str(data, off) + off += new_off + + self.open_end_date, new_off = decode_date_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "TreasureHuntIndividualTasksData": + ret = cls(b"\x00" * 99, 0) + ret.treasure_hunt_individual_task_id = data['TreasureHuntIndividualTaskId'] + ret.treasure_hunt_id = data['TreasureHuntId'] + ret.task_type = data['TaskType'] + ret.quest_scene_id = data['QuestSceneId'] + ret.achievement_possible_num = data['AchievementPossibleNum'] + ret.get_event_point = data['GetEventPoint'] + ret.condition_type = data['ConditionType'] + ret.condition_value_accumulation_flag = data['ConditionValueAccumulationFlag'] + ret.condition_value_1 = data['ConditionValue1'] + ret.condition_value_2 = data['ConditionValue2'] + ret.condition_value_3 = data['ConditionValue3'] + ret.condition_value_4 = data['ConditionValue4'] + ret.condition_value_5 = data['ConditionValue5'] + ret.open_start_date = data['OpenStartDate'] + ret.open_end_date = data['OpenEndDate'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.treasure_hunt_individual_task_id) \ + + encode_int(self.treasure_hunt_id) \ + + encode_byte(self.task_type) \ + + encode_short(self.quest_scene_id) \ + + encode_short(self.achievement_possible_num) \ + + encode_int(self.get_event_point) \ + + encode_byte(self.condition_type) \ + + encode_byte(self.condition_value_accumulation_flag) \ + + encode_str(self.condition_value_1) \ + + encode_str(self.condition_value_2) \ + + encode_str(self.condition_value_3) \ + + encode_str(self.condition_value_4) \ + + encode_str(self.condition_value_5) \ + + encode_date_str(self.open_start_date) \ + + encode_date_str(self.open_end_date) + +class TreasureHuntSpecialEffectsData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.treasure_hunt_special_effect_id = decode_int(data, off) + off += INT_OFF + + self.treasure_hunt_id = decode_int(data, off) + off += INT_OFF + + self.special_effect_type = decode_byte(data, off) + off += BYTE_OFF + + self.special_effect_coefficient, new_off = decode_str(data, off) + off += new_off + + self.target_common_reward_type = decode_byte(data, off) + off += BYTE_OFF + + self.target_common_reward_id = decode_int(data, off) + off += INT_OFF + + self.obtaining_location_type = decode_byte(data, off) + off += BYTE_OFF + + self.obtaining_location_param, new_off = decode_str(data, off) + off += new_off + + self.open_start_date, new_off = decode_date_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "TreasureHuntSpecialEffectsData": + ret = cls(b"\x00" * 99, 0) + ret.treasure_hunt_special_effect_id = data['TreasureHuntSpecialEffectId'] + ret.treasure_hunt_id = data['TreasureHuntId'] + ret.special_effect_type = data['SpecialEffectType'] + ret.special_effect_coefficient = data['SpecialEffectCoefficient'] + ret.target_common_reward_type = data['TargetCommonRewardType'] + ret.target_common_reward_id = data['TargetCommonRewardId'] + ret.obtaining_location_type = data['ObtainingLocationType'] + ret.obtaining_location_param = data['ObtainingLocationParam'] + ret.open_start_date = data['OpenStartDate'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.treasure_hunt_special_effect_id) \ + + encode_int(self.treasure_hunt_id) \ + + encode_byte(self.special_effect_type) \ + + encode_str(self.special_effect_coefficient) \ + + encode_byte(self.target_common_reward_type) \ + + encode_int(self.target_common_reward_id) \ + + encode_byte(self.obtaining_location_type) \ + + encode_str(self.obtaining_location_param) \ + + encode_date_str(self.open_start_date) + +class TreasureHuntEventPointRewardCommonRewardsData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.treasure_hunt_event_point_reward_common_reward_id = decode_int(data, off) + off += INT_OFF + + self.treasure_hunt_id = decode_int(data, off) + off += INT_OFF + + self.need_event_point = decode_int(data, off) + off += INT_OFF + + self.common_reward_type = decode_byte(data, off) + off += BYTE_OFF + + self.common_reward_id = decode_int(data, off) + off += INT_OFF + + self.common_reward_num = decode_short(data, off) + off += SHORT_OFF + + self.strength = decode_int(data, off) + off += INT_OFF + + self.property1_property_id = decode_int(data, off) + off += INT_OFF + + self.property1_value1 = decode_int(data, off) + off += INT_OFF + + self.property1_value2 = decode_int(data, off) + off += INT_OFF + + self.property2_property_id = decode_int(data, off) + off += INT_OFF + + self.property2_value1 = decode_int(data, off) + off += INT_OFF + + self.property2_value2 = decode_int(data, off) + off += INT_OFF + + self.property3_property_id = decode_int(data, off) + off += INT_OFF + + self.property3_value1 = decode_int(data, off) + off += INT_OFF + + self.property3_value2 = decode_int(data, off) + off += INT_OFF + + self.property4_property_id = decode_int(data, off) + off += INT_OFF + + self.property4_value1 = decode_int(data, off) + off += INT_OFF + + self.property4_value2 = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "TreasureHuntEventPointRewardCommonRewardsData": + ret = cls(b"\x00" * 99, 0) + ret.treasure_hunt_event_point_reward_common_reward_id = data['TreasureHuntEventPointRewardCommonRewardId'] + ret.treasure_hunt_id = data['TreasureHuntId'] + ret.need_event_point = data['NeedEventPoint'] + ret.common_reward_type = data['CommonRewardType'] + ret.common_reward_id = data['CommonRewardId'] + ret.common_reward_num = data['CommonRewardNum'] + ret.strength = data['Strength'] + ret.property1_property_id = data['Property1PropertyId'] + ret.property1_value1 = data['Property1Value1'] + ret.property1_value2 = data['Property1Value2'] + ret.property2_property_id = data['Property2PropertyId'] + ret.property2_value1 = data['Property2Value1'] + ret.property2_value2 = data['Property2Value2'] + ret.property3_property_id = data['Property3PropertyId'] + ret.property3_value1 = data['Property3Value1'] + ret.property3_value2 = data['Property3Value2'] + ret.property4_property_id = data['Property4PropertyId'] + ret.property4_value1 = data['Property4Value1'] + ret.property4_value2 = data['Property4Value2'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.treasure_hunt_event_point_reward_common_reward_id) \ + + encode_int(self.treasure_hunt_id) \ + + encode_int(self.need_event_point) \ + + encode_byte(self.common_reward_type) \ + + encode_int(self.common_reward_id) \ + + encode_short(self.common_reward_num) \ + + encode_int(self.strength) \ + + encode_int(self.property1_property_id) \ + + encode_int(self.property1_value1) \ + + encode_int(self.property1_value2) \ + + encode_int(self.property2_property_id) \ + + encode_int(self.property2_value1) \ + + encode_int(self.property2_value2) \ + + encode_int(self.property3_property_id) \ + + encode_int(self.property3_value1) \ + + encode_int(self.property3_value2) \ + + encode_int(self.property4_property_id) \ + + encode_int(self.property4_value1) \ + + encode_int(self.property4_value2) + +class TreasureHuntEventPointRewardTitlesData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.treasure_hunt_event_point_reward_title_id = decode_int(data, off) + off += INT_OFF + + self.treasure_hunt_id = decode_int(data, off) + off += INT_OFF + + self.need_event_point = decode_int(data, off) + off += INT_OFF + + self.title_id = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "TreasureHuntEventPointRewardTitlesData": + ret = cls(b"\x00" * 99, 0) + ret.treasure_hunt_event_point_reward_title_id = data['TreasureHuntEventPointRewardTitleId'] + ret.treasure_hunt_id = data['TreasureHuntId'] + ret.need_event_point = data['NeedEventPoint'] + ret.title_id = data['TitleId'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.treasure_hunt_event_point_reward_title_id) \ + + encode_int(self.treasure_hunt_id) \ + + encode_int(self.need_event_point) \ + + encode_int(self.title_id) + +class TreasureHuntTaskTextData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.treasure_hunt_task_text_id = decode_int(data, off) + off += INT_OFF + + self.task_type = decode_byte(data, off) + off += BYTE_OFF + + self.condition_type = decode_byte(data, off) + off += BYTE_OFF + + self.format, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "TreasureHuntTaskTextData": + ret = cls(b"\x00" * 99, 0) + ret.treasure_hunt_task_text_id = data['TreasureHuntTaskTextId'] + ret.task_type = data['TaskType'] + ret.condition_type = data['ConditionType'] + ret.format = data['Format'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.treasure_hunt_task_text_id) \ + + encode_byte(self.task_type) \ + + encode_byte(self.condition_type) \ + + encode_str(self.format) + +class BnidSerialCodeData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.bnid_item_id, new_off = decode_str(data, off) + off += new_off + + self.serial_code_type = decode_byte(data, off) + off += BYTE_OFF + + self.category = decode_byte(data, off) + off += BYTE_OFF + + self.description, new_off = decode_str(data, off) + off += new_off + + self.open_start_date, new_off = decode_date_str(data, off) + off += new_off + + self.open_end_date, new_off = decode_date_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "BnidSerialCodeData": + ret = cls(b"\x00" * 99, 0) + ret.bnid_item_id = data['BnidItemId'] + ret.serial_code_type = data['SerialCodeType'] + ret.category = data['Category'] + ret.description = data['Description'] + ret.open_start_date = data['OpenStartDate'] + ret.open_end_date = data['OpenEndDate'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_str(self.bnid_item_id) \ + + encode_byte(self.serial_code_type) \ + + encode_byte(self.category) \ + + encode_str(self.description) \ + + encode_date_str(self.open_start_date) \ + + encode_date_str(self.open_end_date) + +class BnidSerialCodeRewardData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.bnid_item_id, new_off = decode_str(data, off) + off += new_off + + self.common_reward_type = decode_byte(data, off) + off += BYTE_OFF + + self.common_reward_id = decode_int(data, off) + off += INT_OFF + + self.common_reward_num = decode_short(data, off) + off += SHORT_OFF + + self.strength = decode_int(data, off) + off += INT_OFF + + self.property1_property_id = decode_int(data, off) + off += INT_OFF + + self.property1_value1 = decode_int(data, off) + off += INT_OFF + + self.property1_value2 = decode_int(data, off) + off += INT_OFF + + self.property2_property_id = decode_int(data, off) + off += INT_OFF + + self.property2_value1 = decode_int(data, off) + off += INT_OFF + + self.property2_value2 = decode_int(data, off) + off += INT_OFF + + self.property3_property_id = decode_int(data, off) + off += INT_OFF + + self.property3_value1 = decode_int(data, off) + off += INT_OFF + + self.property3_value2 = decode_int(data, off) + off += INT_OFF + + self.property4_property_id = decode_int(data, off) + off += INT_OFF + + self.property4_value1 = decode_int(data, off) + off += INT_OFF + + self.property4_value2 = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "BnidSerialCodeRewardData": + ret = cls(b"\x00" * 99, 0) + ret.bnid_item_id = data['BnidItemId'] + ret.common_reward_type = data['CommonRewardType'] + ret.common_reward_id = data['CommonRewardId'] + ret.common_reward_num = data['CommonRewardNum'] + ret.strength = data['Strength'] + ret.property1_property_id = data['Property1PropertyId'] + ret.property1_value1 = data['Property1Value1'] + ret.property1_value2 = data['Property1Value2'] + ret.property2_property_id = data['Property2PropertyId'] + ret.property2_value1 = data['Property2Value1'] + ret.property2_value2 = data['Property2Value2'] + ret.property3_property_id = data['Property3PropertyId'] + ret.property3_value1 = data['Property3Value1'] + ret.property3_value2 = data['Property3Value2'] + ret.property4_property_id = data['Property4PropertyId'] + ret.property4_value1 = data['Property4Value1'] + ret.property4_value2 = data['Property4Value2'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_str(self.bnid_item_id) \ + + encode_byte(self.common_reward_type) \ + + encode_int(self.common_reward_id) \ + + encode_short(self.common_reward_num) \ + + encode_int(self.strength) \ + + encode_int(self.property1_property_id) \ + + encode_int(self.property1_value1) \ + + encode_int(self.property1_value2) \ + + encode_int(self.property2_property_id) \ + + encode_int(self.property2_value1) \ + + encode_int(self.property2_value2) \ + + encode_int(self.property3_property_id) \ + + encode_int(self.property3_value1) \ + + encode_int(self.property3_value2) \ + + encode_int(self.property4_property_id) \ + + encode_int(self.property4_value1) \ + + encode_int(self.property4_value2) + +class SupportLogData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.support_log_id = decode_int(data, off) + off += INT_OFF + + self.chara_id = decode_short(data, off) + off += SHORT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self.rarity = decode_byte(data, off) + off += BYTE_OFF + + self.support_log_type_id = decode_short(data, off) + off += SHORT_OFF + + self.sale_price = decode_int(data, off) + off += INT_OFF + + self.composition_exp = decode_int(data, off) + off += INT_OFF + + self.awakening_exp = decode_int(data, off) + off += INT_OFF + + self.use_rank = decode_short(data, off) + off += SHORT_OFF + + self.group_no = decode_short(data, off) + off += SHORT_OFF + + self.adaptable_appear_append = decode_short(data, off) + off += SHORT_OFF + + self.punisher_appear_append = decode_short(data, off) + off += SHORT_OFF + + self.state, new_off = decode_str(data, off) + off += new_off + + self.passive_state, new_off = decode_str(data, off) + off += new_off + + self.power_default_value = decode_int(data, off) + off += INT_OFF + + self.clt_default_value = decode_int(data, off) + off += INT_OFF + + self.awakening_1_power = decode_int(data, off) + off += INT_OFF + + self.awakening_1_clt = decode_int(data, off) + off += INT_OFF + + self.awakening_2_power = decode_int(data, off) + off += INT_OFF + + self.awakening_2_clt = decode_int(data, off) + off += INT_OFF + + self.awakening_3_power = decode_int(data, off) + off += INT_OFF + + self.awakening_3_clt = decode_int(data, off) + off += INT_OFF + + self.awakening_4_power = decode_int(data, off) + off += INT_OFF + + self.awakening_4_clt = decode_int(data, off) + off += INT_OFF + + self.awakening_5_power = decode_int(data, off) + off += INT_OFF + + self.awakening_5_clt = decode_int(data, off) + off += INT_OFF + + self.normal_leader = decode_int(data, off) + off += INT_OFF + + self.holo_leader = decode_int(data, off) + off += INT_OFF + + self.ui_display_power_title, new_off = decode_str(data, off) + off += new_off + + self.ui_display_power_content, new_off = decode_str(data, off) + off += new_off + + self.prefab, new_off = decode_str(data, off) + off += new_off + + self.cutin_mode, new_off = decode_str(data, off) + off += new_off + + self.skill_voice_id, new_off = decode_str(data, off) + off += new_off + + self.skill_name, new_off = decode_str(data, off) + off += new_off + + self.name_in_skill_cutin, new_off = decode_str(data, off) + off += new_off + + self.skill_text, new_off = decode_str(data, off) + off += new_off + + self.skill_text_in_skill_cutin, new_off = decode_str(data, off) + off += new_off + + self.chara_info, new_off = decode_str(data, off) + off += new_off + + self.cutin_image, new_off = decode_str(data, off) + off += new_off + + self.cutin_image_awake, new_off = decode_str(data, off) + off += new_off + + self.status_chara_icon, new_off = decode_str(data, off) + off += new_off + + self.status_chara_icon_awake, new_off = decode_str(data, off) + off += new_off + + self.collection_display_start_date, new_off = decode_date_str(data, off) + off += new_off + + self.collection_empty_frame_display_flag = decode_byte(data, off) + off += BYTE_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "SupportLogData": + ret = cls(b"\x00" * 99, 0) + ret.support_log_id = data['SupportLogId'] + ret.chara_id = data['CharaId'] + ret.name = data['Name'] + ret.rarity = data['Rarity'] + ret.support_log_type_id = data['SupportLogTypeId'] + ret.sale_price = data['SalePrice'] + ret.composition_exp = data['CompositionExp'] + ret.awakening_exp = data['AwakeningExp'] + ret.use_rank = data['UseRank'] + ret.group_no = data['GroupNo'] + ret.adaptable_appear_append = data['AdaptableAppearAppend'] + ret.punisher_appear_append = data['PunisherAppearAppend'] + ret.state = data['State'] + ret.passive_state = data['PassiveState'] + ret.power_default_value = data['PowerDefaultValue'] + ret.clt_default_value = data['CltDefaultValue'] + ret.awakening_1_power = data['Awakening1Power'] + ret.awakening_1_clt = data['Awakening1Clt'] + ret.awakening_2_power = data['Awakening2Power'] + ret.awakening_2_clt = data['Awakening2Clt'] + ret.awakening_3_power = data['Awakening3Power'] + ret.awakening_3_clt = data['Awakening3Clt'] + ret.awakening_4_power = data['Awakening4Power'] + ret.awakening_4_clt = data['Awakening4Clt'] + ret.awakening_5_power = data['Awakening5Power'] + ret.awakening_5_clt = data['Awakening5Clt'] + ret.normal_leader = data['NormalLeader'] + ret.holo_leader = data['HoloLeader'] + ret.ui_display_power_title = data['UiDisplayPowerTitle'] + ret.ui_display_power_content = data['UiDisplayPowerContent'] + ret.prefab = data['Prefab'] + ret.cutin_mode = data['CutinMode'] + ret.skill_voice_id = data['SkillVoiceId'] + ret.skill_name = data['SkillName'] + ret.name_in_skill_cutin = data['NameInSkillCutin'] + ret.skill_text = data['SkillText'] + ret.skill_text_in_skill_cutin = data['SkillTextInSkillCutin'] + ret.chara_info = data['CharaInfo'] + ret.cutin_image = data['CutinImage'] + ret.cutin_image_awake = data['CutinImageAwake'] + ret.status_chara_icon = data['StatusCharaIcon'] + ret.status_chara_icon_awake = data['StatusCharaIconAwake'] + ret.collection_display_start_date = data['CollectionDisplayStartDate'] + ret.collection_empty_frame_display_flag = data['CollectionEmptyFrameDisplayFlag'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.support_log_id) \ + + encode_short(self.chara_id) \ + + encode_str(self.name) \ + + encode_byte(self.rarity) \ + + encode_short(self.support_log_type_id) \ + + encode_int(self.sale_price) \ + + encode_int(self.composition_exp) \ + + encode_int(self.awakening_exp) \ + + encode_short(self.use_rank) \ + + encode_short(self.group_no) \ + + encode_short(self.adaptable_appear_append) \ + + encode_short(self.punisher_appear_append) \ + + encode_str(self.state) \ + + encode_str(self.passive_state) \ + + encode_int(self.power_default_value) \ + + encode_int(self.clt_default_value) \ + + encode_int(self.awakening_1_power) \ + + encode_int(self.awakening_1_clt) \ + + encode_int(self.awakening_2_power) \ + + encode_int(self.awakening_2_clt) \ + + encode_int(self.awakening_3_power) \ + + encode_int(self.awakening_3_clt) \ + + encode_int(self.awakening_4_power) \ + + encode_int(self.awakening_4_clt) \ + + encode_int(self.awakening_5_power) \ + + encode_int(self.awakening_5_clt) \ + + encode_int(self.normal_leader) \ + + encode_int(self.holo_leader) \ + + encode_str(self.ui_display_power_title) \ + + encode_str(self.ui_display_power_content) \ + + encode_str(self.prefab) \ + + encode_str(self.cutin_mode) \ + + encode_str(self.skill_voice_id) \ + + encode_str(self.skill_name) \ + + encode_str(self.name_in_skill_cutin) \ + + encode_str(self.skill_text) \ + + encode_str(self.skill_text_in_skill_cutin) \ + + encode_str(self.chara_info) \ + + encode_str(self.cutin_image) \ + + encode_str(self.cutin_image_awake) \ + + encode_str(self.status_chara_icon) \ + + encode_str(self.status_chara_icon_awake) \ + + encode_date_str(self.collection_display_start_date) \ + + encode_byte(self.collection_empty_frame_display_flag) + +class SupportLogTypeData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.support_log_type_id = decode_short(data, off) + off += SHORT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "SupportLogTypeData": + ret = cls(b"\x00" * 99, 0) + ret.support_log_type_id = data['SupportLogTypeId'] + ret.name = data['Name'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_short(self.support_log_type_id) \ + + encode_str(self.name) + +class EpisodeAppendData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.episode_append_id = decode_int(data, off) + off += INT_OFF + + self.episode_append_type = decode_byte(data, off) + off += BYTE_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self.flavor_text, new_off = decode_str(data, off) + off += new_off + + self.sale_possible_flag = decode_int(data, off) + off += INT_OFF + + self.sale_price = decode_int(data, off) + off += INT_OFF + + self.episode_append_icon, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "EpisodeAppendData": + ret = cls(b"\x00" * 99, 0) + ret.episode_append_id = data['EpisodeAppendId'] + ret.episode_append_type = data['EpisodeAppendType'] + ret.name = data['Name'] + ret.flavor_text = data['FlavorText'] + ret.sale_possible_flag = data['SalePossibleFlag'] + ret.sale_price = data['SalePrice'] + ret.episode_append_icon = data['EpisodeAppendIcon'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.episode_append_id) \ + + encode_byte(self.episode_append_type) \ + + encode_str(self.name) \ + + encode_str(self.flavor_text) \ + + encode_int(self.sale_possible_flag) \ + + encode_int(self.sale_price) \ + + encode_str(self.episode_append_icon) + +class QuestDefragMatchQuestData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.defrag_match_quest_id = decode_int(data, off) + off += INT_OFF + + self.quest_scene_id = decode_int(data, off) + off += INT_OFF + + self.defrag_match_quest_boss_table_sub_id = decode_int(data, off) + off += INT_OFF + + self.display_name, new_off = decode_str(data, off) + off += new_off + + self.map_no = decode_int(data, off) + off += INT_OFF + + self.unit_data1, new_off = decode_str(data, off) + off += new_off + + self.unit_data2, new_off = decode_str(data, off) + off += new_off + + self.unit_data3, new_off = decode_str(data, off) + off += new_off + + self.adaptable_rate, new_off = decode_str(data, off) + off += new_off + + self.punisher_flag = decode_byte(data, off) + off += BYTE_OFF + + self.comment_details, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "QuestDefragMatchQuestData": + ret = cls(b"\x00" * 99, 0) + ret.defrag_match_quest_id = data['DefragMatchQuestId'] + ret.quest_scene_id = data['QuestSceneId'] + ret.defrag_match_quest_boss_table_sub_id = data['DefragMatchBossTableSubId'] + ret.display_name = data['DisplayName'] + ret.map_no = data['VsModeNo'] + ret.unit_data1 = data['UnitData1'] + ret.unit_data2 = data['UnitData2'] + ret.unit_data3 = data['UnitData3'] + ret.adaptable_rate = data['AdaptableRate'] + ret.punisher_flag = data['PunisherFlag'] + ret.comment_details = data['CommentDetails'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.defrag_match_quest_id) \ + + encode_int(self.quest_scene_id) \ + + encode_int(self.defrag_match_quest_boss_table_sub_id) \ + + encode_str(self.display_name) \ + + encode_int(self.map_no) \ + + encode_str(self.unit_data1) \ + + encode_str(self.unit_data2) \ + + encode_str(self.unit_data3) \ + + encode_str(self.adaptable_rate) \ + + encode_byte(self.punisher_flag) \ + + encode_str(self.comment_details) + +class QuestDefragMatchQuestBossTableData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.defrag_match_quest_boss_table_id = decode_int(data, off) + off += INT_OFF + + self.defrag_match_quest_boss_table_sub_id = decode_int(data, off) + off += INT_OFF + + self.wave = decode_int(data, off) + off += INT_OFF + + self.type = decode_int(data, off) + off += INT_OFF + + self.unit_id = decode_int(data, off) + off += INT_OFF + + self.rate = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "QuestDefragMatchQuestBossTableData": + ret = cls(b"\x00" * 99, 0) + ret.defrag_match_quest_boss_table_id = data['DefragMatchBossTableId'] + ret.defrag_match_quest_boss_table_sub_id = data['DefragMatchBossTableSubId'] + ret.wave = data['Wave'] + ret.type = data['Type'] + ret.unit_id = data['UnitId'] + ret.rate = data['Rate'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.defrag_match_quest_boss_table_id) \ + + encode_int(self.defrag_match_quest_boss_table_sub_id) \ + + encode_int(self.wave) \ + + encode_int(self.type) \ + + encode_int(self.unit_id) \ + + encode_int(self.rate) + +class DefragMatchData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.defrag_match_id = decode_int(data, off) + off += INT_OFF + + self.season_no = decode_short(data, off) + off += SHORT_OFF + + self.title, new_off = decode_str(data, off) + off += new_off + + self.first_attack_bonus_coefficient, new_off = decode_str(data, off) + off += new_off + + self.last_attack_bonus_coefficient, new_off = decode_str(data, off) + off += new_off + + self.memo, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "DefragMatchData": + ret = cls(b"\x00" * 99, 0) + ret.defrag_match_id = data['DefragMatchId'] + ret.season_no = data['SeasonNo'] + ret.title = data['Title'] + ret.first_attack_bonus_coefficient = data['FirstAttackBonusCoefficient'] + ret.last_attack_bonus_coefficient = data['LastAttackBonusCoefficient'] + ret.memo = data['Memo'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.defrag_match_id) \ + + encode_short(self.season_no) \ + + encode_str(self.title) \ + + encode_str(self.first_attack_bonus_coefficient) \ + + encode_str(self.last_attack_bonus_coefficient) \ + + encode_str(self.memo) + +class DefragMatchSeedData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.defrag_match_seed_id = decode_int(data, off) + off += INT_OFF + + self.defrag_match_id = decode_int(data, off) + off += INT_OFF + + self.check_type = decode_byte(data, off) + off += BYTE_OFF + + self.need_class_num = decode_short(data, off) + off += SHORT_OFF + + self.need_cleared_trial_tower_id = decode_short(data, off) + off += SHORT_OFF + + self.get_league_point = decode_int(data, off) + off += INT_OFF + + self.get_league_score = decode_short(data, off) + off += SHORT_OFF + + self.set_class_num = decode_short(data, off) + off += SHORT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "DefragMatchSeedData": + ret = cls(b"\x00" * 99, 0) + ret.defrag_match_seed_id = data['DefragMatchSeedId'] + ret.defrag_match_id = data['DefragMatchId'] + ret.check_type = data['CheckType'] + ret.need_class_num = data['NeedClassNum'] + ret.need_cleared_trial_tower_id = data['NeedClearedTrialTowerId'] + ret.get_league_point = data['GetLeaguePoint'] + ret.get_league_score = data['GetLeagueScore'] + ret.set_class_num = data['SetClassNum'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.defrag_match_seed_id) \ + + encode_int(self.defrag_match_id) \ + + encode_byte(self.check_type) \ + + encode_short(self.need_class_num) \ + + encode_short(self.need_cleared_trial_tower_id) \ + + encode_int(self.get_league_point) \ + + encode_short(self.get_league_score) \ + + encode_short(self.set_class_num) + +class DefragMatchSpecialEffectData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.defrag_match_special_effec_id = decode_int(data, off) + off += INT_OFF + + self.defrag_match_id = decode_int(data, off) + off += INT_OFF + + self.special_effect_type = decode_byte(data, off) + off += BYTE_OFF + + self.special_effect_coefficient, new_off = decode_str(data, off) + off += new_off + + self.target_chara_id = decode_short(data, off) + off += SHORT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "DefragMatchSpecialEffectData": + ret = cls(b"\x00" * 99, 0) + ret.defrag_match_special_effec_id = data['DefragMatchSpecialEffectsId'] + ret.defrag_match_id = data['DefragMatchId'] + ret.special_effect_type = data['SpecialEffectType'] + ret.special_effect_coefficient = data['SpecialEffectCoefficient'] + ret.target_chara_id = data['TargetCharaId'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.defrag_match_special_effec_id) \ + + encode_int(self.defrag_match_id) \ + + encode_byte(self.special_effect_type) \ + + encode_str(self.special_effect_coefficient) \ + + encode_short(self.target_chara_id) + +class DefragMatchGradeData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.defrag_match_grade_id = decode_int(data, off) + off += INT_OFF + + self.defrag_match_id = decode_int(data, off) + off += INT_OFF + + self.class_num = decode_short(data, off) + off += SHORT_OFF + + self.grade_name, new_off = decode_str(data, off) + off += new_off + + self.class_name, new_off = decode_str(data, off) + off += new_off + + self.promotion_line_league_score = decode_short(data, off) + off += SHORT_OFF + + self.demotion_line_league_score = decode_short(data, off) + off += SHORT_OFF + + self.league_score_decrease_flag = decode_byte(data, off) + off += BYTE_OFF + + self.target_league_point_1 = decode_int(data, off) + off += INT_OFF + + self.target_league_point_2 = decode_int(data, off) + off += INT_OFF + + self.mvp_add_league_point = decode_int(data, off) + off += INT_OFF + + self.mob_level = decode_short(data, off) + off += SHORT_OFF + + self.normal_boss_level = decode_short(data, off) + off += SHORT_OFF + + self.adaptable_level = decode_short(data, off) + off += SHORT_OFF + + self.punisher_level = decode_short(data, off) + off += SHORT_OFF + + self.punisher_appearance_rate, new_off = decode_str(data, off) + off += new_off + + self.cpu_level = decode_short(data, off) + off += SHORT_OFF + + self.record_medal_drop_num = decode_short(data, off) + off += SHORT_OFF + + self.target_league_point_coefficient, new_off = decode_str(data, off) + off += new_off + + self.reward_table_sub_id = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "DefragMatchGradeData": + ret = cls(b"\x00" * 99, 0) + ret.defrag_match_grade_id = data['DefragMatchGradeId'] + ret.defrag_match_id = data['DefragMatchId'] + ret.class_num = data['ClassNum'] + ret.grade_name = data['GradeName'] + ret.class_name = data['ClassName'] + ret.promotion_line_league_score = data['PromotionLineLeagueScore'] + ret.demotion_line_league_score = data['DemotionLineLeagueScore'] + ret.league_score_decrease_flag = data['LeagueScoreDecreaseFlag'] + ret.target_league_point_1 = data['TargetLeaguePoint1'] + ret.target_league_point_2 = data['TargetLeaguePoint2'] + ret.mvp_add_league_point = data['MvpAddLeaguePoint'] + ret.mob_level = data['MobLevel'] + ret.normal_boss_level = data['NormalBossLevel'] + ret.adaptable_level = data['AdaptableLevel'] + ret.punisher_level = data['PunisherLevel'] + ret.punisher_appearance_rate = data['PunisherAppearanceRate'] + ret.cpu_level = data['CpuLevel'] + ret.record_medal_drop_num = data['RecordMedalDropNum'] + ret.target_league_point_coefficient = data['TargetLeaguePointCoefficient'] + ret.reward_table_sub_id = data['RewardTableSubId'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.defrag_match_grade_id) \ + + encode_int(self.defrag_match_id) \ + + encode_short(self.class_num) \ + + encode_str(self.grade_name) \ + + encode_str(self.class_name) \ + + encode_short(self.promotion_line_league_score) \ + + encode_short(self.demotion_line_league_score) \ + + encode_byte(self.league_score_decrease_flag) \ + + encode_int(self.target_league_point_1) \ + + encode_int(self.target_league_point_2) \ + + encode_int(self.mvp_add_league_point) \ + + encode_short(self.mob_level) \ + + encode_short(self.normal_boss_level) \ + + encode_short(self.adaptable_level) \ + + encode_short(self.punisher_level) \ + + encode_str(self.punisher_appearance_rate) \ + + encode_short(self.cpu_level) \ + + encode_short(self.record_medal_drop_num) \ + + encode_str(self.target_league_point_coefficient) \ + + encode_int(self.reward_table_sub_id) + +class DefragMatchCpuUnitData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.defrag_match_cpu_unit_id = decode_int(data, off) + off += INT_OFF + + self.appearance_start_defrag_match_id = decode_int(data, off) + off += INT_OFF + + self.appearance_end_defrag_match_id = decode_int(data, off) + off += INT_OFF + + self.appearance_start_class_num = decode_int(data, off) + off += INT_OFF + + self.appearance_end_class_num = decode_int(data, off) + off += INT_OFF + + self.hero_log_hero_log_id = decode_int(data, off) + off += INT_OFF + + self.hero_log_log_level = decode_short(data, off) + off += SHORT_OFF + + self.hero_log_awakening_stage = decode_short(data, off) + off += SHORT_OFF + + self.hero_log_property1_property_id = decode_int(data, off) + off += INT_OFF + + self.hero_log_property1_value1 = decode_int(data, off) + off += INT_OFF + + self.hero_log_property1_value2 = decode_int(data, off) + off += INT_OFF + + self.hero_log_property2_property_id = decode_int(data, off) + off += INT_OFF + + self.hero_log_property2_value1 = decode_int(data, off) + off += INT_OFF + + self.hero_log_property2_value2 = decode_int(data, off) + off += INT_OFF + + self.hero_log_property3_property_id = decode_int(data, off) + off += INT_OFF + + self.hero_log_property3_value1 = decode_int(data, off) + off += INT_OFF + + self.hero_log_property3_value2 = decode_int(data, off) + off += INT_OFF + + self.hero_log_property4_property_id = decode_int(data, off) + off += INT_OFF + + self.hero_log_property4_value1 = decode_int(data, off) + off += INT_OFF + + self.hero_log_property4_value2 = decode_int(data, off) + off += INT_OFF + + self.main_weapon_equipment_id = decode_int(data, off) + off += INT_OFF + + self.main_weapon_enhancement_value = decode_short(data, off) + off += SHORT_OFF + + self.main_weapon_awakening_stage = decode_short(data, off) + off += SHORT_OFF + + self.main_weapon_property1_property_id = decode_int(data, off) + off += INT_OFF + + self.main_weapon_property1_value1 = decode_int(data, off) + off += INT_OFF + + self.main_weapon_property1_value2 = decode_int(data, off) + off += INT_OFF + + self.main_weapon_property2_property_id = decode_int(data, off) + off += INT_OFF + + self.main_weapon_property2_value1 = decode_int(data, off) + off += INT_OFF + + self.main_weapon_property2_value2 = decode_int(data, off) + off += INT_OFF + + self.main_weapon_property3_property_id = decode_int(data, off) + off += INT_OFF + + self.main_weapon_property3_value1 = decode_int(data, off) + off += INT_OFF + + self.main_weapon_property3_value2 = decode_int(data, off) + off += INT_OFF + + self.main_weapon_property4_property_id = decode_int(data, off) + off += INT_OFF + + self.main_weapon_property4_value1 = decode_int(data, off) + off += INT_OFF + + self.main_weapon_property4_value2 = decode_int(data, off) + off += INT_OFF + + self.sub_equipment_equipment_id = decode_int(data, off) + off += INT_OFF + + self.sub_equipment_enhancement_value = decode_short(data, off) + off += SHORT_OFF + + self.sub_equipment_awakening_stage = decode_short(data, off) + off += SHORT_OFF + + self.sub_equipment_property1_property_id = decode_int(data, off) + off += INT_OFF + + self.sub_equipment_property1_value1 = decode_int(data, off) + off += INT_OFF + + self.sub_equipment_property1_value2 = decode_int(data, off) + off += INT_OFF + + self.sub_equipment_property2_property_id = decode_int(data, off) + off += INT_OFF + + self.sub_equipment_property2_value1 = decode_int(data, off) + off += INT_OFF + + self.sub_equipment_property2_value2 = decode_int(data, off) + off += INT_OFF + + self.sub_equipment_property3_property_id = decode_int(data, off) + off += INT_OFF + + self.sub_equipment_property3_value1 = decode_int(data, off) + off += INT_OFF + + self.sub_equipment_property3_value2 = decode_int(data, off) + off += INT_OFF + + self.sub_equipment_property4_property_id = decode_int(data, off) + off += INT_OFF + + self.sub_equipment_property4_value1 = decode_int(data, off) + off += INT_OFF + + self.sub_equipment_property4_value2 = decode_int(data, off) + off += INT_OFF + + self.skill_slot1_skill_id = decode_short(data, off) + off += SHORT_OFF + + self.skill_slot2_skill_id = decode_short(data, off) + off += SHORT_OFF + + self.skill_slot3_skill_id = decode_short(data, off) + off += SHORT_OFF + + self.skill_slot4_skill_id = decode_short(data, off) + off += SHORT_OFF + + self.skill_slot5_skill_id = decode_short(data, off) + off += SHORT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "DefragMatchCpuUnitData": + ret = cls(b"\x00" * 99, 0) + ret.defrag_match_cpu_unit_id = data['DefragMatchCpuUnitsId'] + ret.appearance_start_defrag_match_id = data['AppearanceStartDefragMatchId'] + ret.appearance_end_defrag_match_id = data['AppearanceEndDefragMatchId'] + ret.appearance_start_class_num = data['AppearanceStartClassNum'] + ret.appearance_end_class_num = data['AppearanceEndClassNum'] + ret.hero_log_hero_log_id = data['HeroLogHeroLogId'] + ret.hero_log_log_level = data['HeroLogLogLevel'] + ret.hero_log_awakening_stage = data['HeroLogAwakeningStage'] + ret.hero_log_property1_property_id = data['HeroLogProperty1PropertyId'] + ret.hero_log_property1_value1 = data['HeroLogProperty1Value1'] + ret.hero_log_property1_value2 = data['HeroLogProperty1Value2'] + ret.hero_log_property2_property_id = data['HeroLogProperty2PropertyId'] + ret.hero_log_property2_value1 = data['HeroLogProperty2Value1'] + ret.hero_log_property2_value2 = data['HeroLogProperty2Value2'] + ret.hero_log_property3_property_id = data['HeroLogProperty3PropertyId'] + ret.hero_log_property3_value1 = data['HeroLogProperty3Value1'] + ret.hero_log_property3_value2 = data['HeroLogProperty3Value2'] + ret.hero_log_property4_property_id = data['HeroLogProperty4PropertyId'] + ret.hero_log_property4_value1 = data['HeroLogProperty4Value1'] + ret.hero_log_property4_value2 = data['HeroLogProperty4Value2'] + ret.main_weapon_equipment_id = data['MainWeaponEquipmentId'] + ret.main_weapon_enhancement_value = data['MainWeaponEnhancementValue'] + ret.main_weapon_awakening_stage = data['MainWeaponAwakeningStage'] + ret.main_weapon_property1_property_id = data['MainWeaponProperty1PropertyId'] + ret.main_weapon_property1_value1 = data['MainWeaponProperty1Value1'] + ret.main_weapon_property1_value2 = data['MainWeaponProperty1Value2'] + ret.main_weapon_property2_property_id = data['MainWeaponProperty2PropertyId'] + ret.main_weapon_property2_value1 = data['MainWeaponProperty2Value1'] + ret.main_weapon_property2_value2 = data['MainWeaponProperty2Value2'] + ret.main_weapon_property3_property_id = data['MainWeaponProperty3PropertyId'] + ret.main_weapon_property3_value1 = data['MainWeaponProperty3Value1'] + ret.main_weapon_property3_value2 = data['MainWeaponProperty3Value2'] + ret.main_weapon_property4_property_id = data['MainWeaponProperty4PropertyId'] + ret.main_weapon_property4_value1 = data['MainWeaponProperty4Value1'] + ret.main_weapon_property4_value2 = data['MainWeaponProperty4Value2'] + ret.sub_equipment_equipment_id = data['SubEquipmentEquipmentId'] + ret.sub_equipment_enhancement_value = data['SubEquipmentEnhancementValue'] + ret.sub_equipment_awakening_stage = data['SubEquipmentAwakeningStage'] + ret.sub_equipment_property1_property_id = data['SubEquipmentProperty1PropertyId'] + ret.sub_equipment_property1_value1 = data['SubEquipmentProperty1Value1'] + ret.sub_equipment_property1_value2 = data['SubEquipmentProperty1Value2'] + ret.sub_equipment_property2_property_id = data['SubEquipmentProperty2PropertyId'] + ret.sub_equipment_property2_value1 = data['SubEquipmentProperty2Value1'] + ret.sub_equipment_property2_value2 = data['SubEquipmentProperty2Value2'] + ret.sub_equipment_property3_property_id = data['SubEquipmentProperty3PropertyId'] + ret.sub_equipment_property3_value1 = data['SubEquipmentProperty3Value1'] + ret.sub_equipment_property3_value2 = data['SubEquipmentProperty3Value2'] + ret.sub_equipment_property4_property_id = data['SubEquipmentProperty4PropertyId'] + ret.sub_equipment_property4_value1 = data['SubEquipmentProperty4Value1'] + ret.sub_equipment_property4_value2 = data['SubEquipmentProperty4Value2'] + ret.skill_slot1_skill_id = data['SkillSlot1SkillId'] + ret.skill_slot2_skill_id = data['SkillSlot2SkillId'] + ret.skill_slot3_skill_id = data['SkillSlot3SkillId'] + ret.skill_slot4_skill_id = data['SkillSlot4SkillId'] + ret.skill_slot5_skill_id = data['SkillSlot5SkillId'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.defrag_match_cpu_unit_id) \ + + encode_int(self.appearance_start_defrag_match_id) \ + + encode_int(self.appearance_end_defrag_match_id) \ + + encode_int(self.appearance_start_class_num) \ + + encode_int(self.appearance_end_class_num) \ + + encode_int(self.hero_log_hero_log_id) \ + + encode_short(self.hero_log_log_level) \ + + encode_short(self.hero_log_awakening_stage) \ + + encode_int(self.hero_log_property1_property_id) \ + + encode_int(self.hero_log_property1_value1) \ + + encode_int(self.hero_log_property1_value2) \ + + encode_int(self.hero_log_property2_property_id) \ + + encode_int(self.hero_log_property2_value1) \ + + encode_int(self.hero_log_property2_value2) \ + + encode_int(self.hero_log_property3_property_id) \ + + encode_int(self.hero_log_property3_value1) \ + + encode_int(self.hero_log_property3_value2) \ + + encode_int(self.hero_log_property4_property_id) \ + + encode_int(self.hero_log_property4_value1) \ + + encode_int(self.hero_log_property4_value2) \ + + encode_int(self.main_weapon_equipment_id) \ + + encode_short(self.main_weapon_enhancement_value) \ + + encode_short(self.main_weapon_awakening_stage) \ + + encode_int(self.main_weapon_property1_property_id) \ + + encode_int(self.main_weapon_property1_value1) \ + + encode_int(self.main_weapon_property1_value2) \ + + encode_int(self.main_weapon_property2_property_id) \ + + encode_int(self.main_weapon_property2_value1) \ + + encode_int(self.main_weapon_property2_value2) \ + + encode_int(self.main_weapon_property3_property_id) \ + + encode_int(self.main_weapon_property3_value1) \ + + encode_int(self.main_weapon_property3_value2) \ + + encode_int(self.main_weapon_property4_property_id) \ + + encode_int(self.main_weapon_property4_value1) \ + + encode_int(self.main_weapon_property4_value2) \ + + encode_int(self.sub_equipment_equipment_id) \ + + encode_short(self.sub_equipment_enhancement_value) \ + + encode_short(self.sub_equipment_awakening_stage) \ + + encode_int(self.sub_equipment_property1_property_id) \ + + encode_int(self.sub_equipment_property1_value1) \ + + encode_int(self.sub_equipment_property1_value2) \ + + encode_int(self.sub_equipment_property2_property_id) \ + + encode_int(self.sub_equipment_property2_value1) \ + + encode_int(self.sub_equipment_property2_value2) \ + + encode_int(self.sub_equipment_property3_property_id) \ + + encode_int(self.sub_equipment_property3_value1) \ + + encode_int(self.sub_equipment_property3_value2) \ + + encode_int(self.sub_equipment_property4_property_id) \ + + encode_int(self.sub_equipment_property4_value1) \ + + encode_int(self.sub_equipment_property4_value2) \ + + encode_short(self.skill_slot1_skill_id) \ + + encode_short(self.skill_slot2_skill_id) \ + + encode_short(self.skill_slot3_skill_id) \ + + encode_short(self.skill_slot4_skill_id) \ + + encode_short(self.skill_slot5_skill_id) + +class DefragMatchCpuSupportLogData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.defrag_match_cpu_support_log_id = decode_int(data, off) + off += INT_OFF + + self.appearance_start_defrag_match_id = decode_int(data, off) + off += INT_OFF + + self.appearance_end_defrag_match_id = decode_int(data, off) + off += INT_OFF + + self.appearance_start_class_num = decode_int(data, off) + off += INT_OFF + + self.appearance_end_class_num = decode_int(data, off) + off += INT_OFF + + self.support_log_id = decode_int(data, off) + off += INT_OFF + + self.awakening_stage = decode_short(data, off) + off += SHORT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "DefragMatchCpuSupportLogData": + ret = cls(b"\x00" * 99, 0) + ret.defrag_match_cpu_support_log_id = data['DefragMatchCpuSupportLogId'] + ret.appearance_start_defrag_match_id = data['AppearanceStartDefragMatchId'] + ret.appearance_end_defrag_match_id = data['AppearanceEndDefragMatchId'] + ret.appearance_start_class_num = data['AppearanceStartClassNum'] + ret.appearance_end_class_num = data['AppearanceEndClassNum'] + ret.support_log_id = data['SupportLogId'] + ret.awakening_stage = data['AwakeningStage'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.defrag_match_cpu_support_log_id) \ + + encode_int(self.appearance_start_defrag_match_id) \ + + encode_int(self.appearance_end_defrag_match_id) \ + + encode_int(self.appearance_start_class_num) \ + + encode_int(self.appearance_end_class_num) \ + + encode_int(self.support_log_id) \ + + encode_short(self.awakening_stage) + +class DefragMatchPeriodBonusData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.defrag_match_period_bonus_id = decode_int(data, off) + off += INT_OFF + + self.defrag_match_id = decode_int(data, off) + off += INT_OFF + + self.bonus_type = decode_byte(data, off) + off += BYTE_OFF + + self.param_1, new_off = decode_str(data, off) + off += new_off + + self.param_2, new_off = decode_str(data, off) + off += new_off + + self.param_3, new_off = decode_str(data, off) + off += new_off + + self.start_date, new_off = decode_date_str(data, off) + off += new_off + + self.end_date, new_off = decode_date_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "DefragMatchPeriodBonusData": + ret = cls(b"\x00" * 99, 0) + ret.defrag_match_period_bonus_id = data['DefragMatchPeriodBonusId'] + ret.defrag_match_id = data['DefragMatchId'] + ret.bonus_type = data['BonusType'] + ret.param_1 = data['Param1'] + ret.param_2 = data['Param2'] + ret.param_3 = data['Param3'] + ret.start_date = data['StartDate'] + ret.end_date = data['EndDate'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.defrag_match_period_bonus_id) \ + + encode_int(self.defrag_match_id) \ + + encode_byte(self.bonus_type) \ + + encode_str(self.param_1) \ + + encode_str(self.param_2) \ + + encode_str(self.param_3) \ + + encode_date_str(self.start_date) \ + + encode_date_str(self.end_date) + +class DefragMatchRandomBonusTableData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.defrag_match_random_bonus_id = decode_int(data, off) + off += INT_OFF + + self.defrag_match_id = decode_int(data, off) + off += INT_OFF + + self.defrag_match_random_bonus_condition_id = decode_int(data, off) + off += INT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self.condition_value1 = decode_int(data, off) + off += INT_OFF + + self.condition_value2 = decode_int(data, off) + off += INT_OFF + + self.get_league_point = decode_int(data, off) + off += INT_OFF + + self.random_bonus_num = decode_byte(data, off) + off += BYTE_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "DefragMatchRandomBonusTableData": + ret = cls(b"\x00" * 99, 0) + ret.defrag_match_random_bonus_id = data['DefragMatchRandomBonusId'] + ret.defrag_match_id = data['DefragMatchId'] + ret.defrag_match_random_bonus_condition_id = data['DefragMatchRandomBonusConditionId'] + ret.name = data['Name'] + ret.condition_value1 = data['ConditionValue1'] + ret.condition_value2 = data['ConditionValue2'] + ret.get_league_point = data['GetLeaguePoint'] + ret.random_bonus_num = data['RandomBonusNum'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.defrag_match_random_bonus_id) \ + + encode_int(self.defrag_match_id) \ + + encode_int(self.defrag_match_random_bonus_condition_id) \ + + encode_str(self.name) \ + + encode_int(self.condition_value1) \ + + encode_int(self.condition_value2) \ + + encode_int(self.get_league_point) \ + + encode_byte(self.random_bonus_num) + +class DefragMatchRandomBonusConditionData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.defrag_match_random_bonus_condition_id = decode_int(data, off) + off += INT_OFF + + self.format, new_off = decode_str(data, off) + off += new_off + + self.hud_format, new_off = decode_str(data, off) + off += new_off + + self.format_param_size = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "DefragMatchRandomBonusConditionData": + ret = cls(b"\x00" * 99, 0) + ret.defrag_match_random_bonus_condition_id = data['DefragMatchRandomBonusConditionId'] + ret.format = data['Format'] + ret.hud_format = data['HudFormat'] + ret.format_param_size = data['FormatParamSize'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.defrag_match_random_bonus_condition_id) \ + + encode_str(self.format) \ + + encode_str(self.hud_format) \ + + encode_int(self.format_param_size) + +class DefragMatchRareDropData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.defrag_match_rare_drop_id = decode_int(data, off) + off += INT_OFF + + self.defrag_match_id = decode_int(data, off) + off += INT_OFF + + self.unit_id = decode_int(data, off) + off += INT_OFF + + self.common_reward_type = decode_int(data, off) + off += INT_OFF + + self.common_reward_id = decode_int(data, off) + off += INT_OFF + + self.common_reward_num = decode_int(data, off) + off += INT_OFF + + self.strength_min = decode_int(data, off) + off += INT_OFF + + self.strength_max = decode_int(data, off) + off += INT_OFF + + self.property_table_sub_id = decode_int(data, off) + off += INT_OFF + + self.drop_rate, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "DefragMatchRareDropData": + ret = cls(b"\x00" * 99, 0) + ret.defrag_match_rare_drop_id = data['DefragMatchRareDropId'] + ret.defrag_match_id = data['DefragMatchId'] + ret.unit_id = data['UnitId'] + ret.common_reward_type = data['CommonRewardType'] + ret.common_reward_id = data['CommonRewardId'] + ret.common_reward_num = data['CommonRewardNum'] + ret.strength_min = data['StrengthMin'] + ret.strength_max = data['StrengthMax'] + ret.property_table_sub_id = data['PropertyTableSubId'] + ret.drop_rate = data['DropRate'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.defrag_match_rare_drop_id) \ + + encode_int(self.defrag_match_id) \ + + encode_int(self.unit_id) \ + + encode_int(self.common_reward_type) \ + + encode_int(self.common_reward_id) \ + + encode_int(self.common_reward_num) \ + + encode_int(self.strength_min) \ + + encode_int(self.strength_max) \ + + encode_int(self.property_table_sub_id) \ + + encode_str(self.drop_rate) + +class YuiMedalShopData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.yui_medal_shop_id = decode_int(data, off) + off += INT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self.description, new_off = decode_str(data, off) + off += new_off + + self.selling_yui_medal = decode_short(data, off) + off += SHORT_OFF + + self.selling_col = decode_int(data, off) + off += INT_OFF + + self.selling_event_item_id = decode_int(data, off) + off += INT_OFF + + self.selling_event_item_num = decode_int(data, off) + off += INT_OFF + + self.selling_ticket_num = decode_int(data, off) + off += INT_OFF + + self.purchase_limit = decode_short(data, off) + off += SHORT_OFF + + self.pick_up_flag = decode_byte(data, off) + off += BYTE_OFF + + self.product_category = decode_byte(data, off) + off += BYTE_OFF + + self.sales_type = decode_byte(data, off) + off += BYTE_OFF + + self.target_days = decode_byte(data, off) + off += BYTE_OFF + + self.target_hour = decode_byte(data, off) + off += BYTE_OFF + + self.interval_hour = decode_byte(data, off) + off += BYTE_OFF + + self.sales_start_date, new_off = decode_date_str(data, off) + off += new_off + + self.sales_end_date, new_off = decode_date_str(data, off) + off += new_off + + self.sort = decode_byte(data, off) + off += BYTE_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "YuiMedalShopData": + ret = cls(b"\x00" * 99, 0) + ret.yui_medal_shop_id = data['YuiMedalShopId'] + ret.name = data['Name'] + ret.description = data['Description'] + ret.selling_yui_medal = data['SellingYuiMedal'] + ret.selling_col = data['SellingCol'] + ret.selling_event_item_id = data['SellingEventItemId'] + ret.selling_event_item_num = data['SellingEventItemNum'] + ret.selling_ticket_num = data['SellingTicketNum'] + ret.purchase_limit = data['PurchaseLimit'] + ret.pick_up_flag = data['PickUpFlag'] + ret.product_category = data['ProductCategory'] + ret.sales_type = data['SalesType'] + ret.target_days = data['TargetDays'] + ret.target_hour = data['TargetHour'] + ret.interval_hour = data['IntervalHour'] + ret.sales_start_date = data['SalesStartDate'] + ret.sales_end_date = data['SalesEndDate'] + ret.sort = data['Sort'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.yui_medal_shop_id) \ + + encode_str(self.name) \ + + encode_str(self.description) \ + + encode_short(self.selling_yui_medal) \ + + encode_int(self.selling_col) \ + + encode_int(self.selling_event_item_id) \ + + encode_int(self.selling_event_item_num) \ + + encode_int(self.selling_ticket_num) \ + + encode_short(self.purchase_limit) \ + + encode_byte(self.pick_up_flag) \ + + encode_byte(self.product_category) \ + + encode_byte(self.sales_type) \ + + encode_byte(self.target_days) \ + + encode_byte(self.target_hour) \ + + encode_byte(self.interval_hour) \ + + encode_date_str(self.sales_start_date) \ + + encode_date_str(self.sales_end_date) \ + + encode_byte(self.sort) + +class YuiMedalShopItemData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.yui_medal_shop_item_id = decode_int(data, off) + off += INT_OFF + + self.yui_medal_shop_id = decode_int(data, off) + off += INT_OFF + + self.common_reward_type = decode_byte(data, off) + off += BYTE_OFF + + self.common_reward_id = decode_int(data, off) + off += INT_OFF + + self.common_reward_num = decode_short(data, off) + off += SHORT_OFF + + self.strength = decode_int(data, off) + off += INT_OFF + + self.property1_property_id = decode_int(data, off) + off += INT_OFF + + self.property1_value1 = decode_int(data, off) + off += INT_OFF + + self.property1_value2 = decode_int(data, off) + off += INT_OFF + + self.property2_property_id = decode_int(data, off) + off += INT_OFF + + self.property2_value1 = decode_int(data, off) + off += INT_OFF + + self.property2_value2 = decode_int(data, off) + off += INT_OFF + + self.property3_property_id = decode_int(data, off) + off += INT_OFF + + self.property3_value1 = decode_int(data, off) + off += INT_OFF + + self.property3_value2 = decode_int(data, off) + off += INT_OFF + + self.property4_property_id = decode_int(data, off) + off += INT_OFF + + self.property4_value1 = decode_int(data, off) + off += INT_OFF + + self.property4_value2 = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "YuiMedalShopItemData": + ret = cls(b"\x00" * 99, 0) + ret.yui_medal_shop_item_id = data['YuiMedalShopItemId'] + ret.yui_medal_shop_id = data['YuiMedalShopId'] + ret.common_reward_type = data['CommonRewardType'] + ret.common_reward_id = data['CommonRewardId'] + ret.common_reward_num = data['CommonRewardNum'] + ret.strength = data['Strength'] + ret.property1_property_id = data['Property1PropertyId'] + ret.property1_value1 = data['Property1Value1'] + ret.property1_value2 = data['Property1Value2'] + ret.property2_property_id = data['Property2PropertyId'] + ret.property2_value1 = data['Property2Value1'] + ret.property2_value2 = data['Property2Value2'] + ret.property3_property_id = data['Property3PropertyId'] + ret.property3_value1 = data['Property3Value1'] + ret.property3_value2 = data['Property3Value2'] + ret.property4_property_id = data['Property4PropertyId'] + ret.property4_value1 = data['Property4Value1'] + ret.property4_value2 = data['Property4Value2'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.yui_medal_shop_item_id) \ + + encode_int(self.yui_medal_shop_id) \ + + encode_byte(self.common_reward_type) \ + + encode_int(self.common_reward_id) \ + + encode_short(self.common_reward_num) \ + + encode_int(self.strength) \ + + encode_int(self.property1_property_id) \ + + encode_int(self.property1_value1) \ + + encode_int(self.property1_value2) \ + + encode_int(self.property2_property_id) \ + + encode_int(self.property2_value1) \ + + encode_int(self.property2_value2) \ + + encode_int(self.property3_property_id) \ + + encode_int(self.property3_value1) \ + + encode_int(self.property3_value2) \ + + encode_int(self.property4_property_id) \ + + encode_int(self.property4_value1) \ + + encode_int(self.property4_value2) + +class EventSceneData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.event_scene_id = decode_int(data, off) + off += INT_OFF + + self.event_id = decode_int(data, off) + off += INT_OFF + + self.scene_type = decode_byte(data, off) + off += BYTE_OFF + + self.episode_no = decode_int(data, off) + off += INT_OFF + + self.title, new_off = decode_str(data, off) + off += new_off + + self.param_1, new_off = decode_str(data, off) + off += new_off + + self.param_2, new_off = decode_str(data, off) + off += new_off + + self.param_3, new_off = decode_str(data, off) + off += new_off + + self.adv_name, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "EventSceneData": + ret = cls(b"\x00" * 99, 0) + ret.event_scene_id = data['EventSceneId'] + ret.event_id = data['EventId'] + ret.scene_type = data['SceneType'] + ret.episode_no = data['EpisodeNo'] + ret.title = data['Title'] + ret.param_1 = data['Param1'] + ret.param_2 = data['Param2'] + ret.param_3 = data['Param3'] + ret.adv_name = data['AdvName'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.event_scene_id) \ + + encode_int(self.event_id) \ + + encode_byte(self.scene_type) \ + + encode_int(self.episode_no) \ + + encode_str(self.title) \ + + encode_str(self.param_1) \ + + encode_str(self.param_2) \ + + encode_str(self.param_3) \ + + encode_str(self.adv_name) + +class GenericCampaignPeriodData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.generic_campaign_period_id = decode_int(data, off) + off += INT_OFF + + self.start_date, new_off = decode_date_str(data, off) + off += new_off + + self.end_date, new_off = decode_date_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "GenericCampaignPeriodData": + ret = cls(b"\x00" * 99, 0) + ret.generic_campaign_period_id = data['GenericCampaignPeriodId'] + ret.start_date = data['StartDate'] + ret.end_date = data['EndDate'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.generic_campaign_period_id) \ + + encode_date_str(self.start_date) \ + + encode_date_str(self.end_date) + +class BeginnerMissionData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.beginner_mission_id = decode_int(data, off) + off += INT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self.start_date, new_off = decode_date_str(data, off) + off += new_off + + self.end_date, new_off = decode_date_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "BeginnerMissionData": + ret = cls(b"\x00" * 99, 0) + ret.beginner_mission_id = data['BeginnerMissionId'] + ret.name = data['Name'] + ret.start_date = data['StartDate'] + ret.end_date = data['EndDate'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.beginner_mission_id) \ + + encode_str(self.name) \ + + encode_date_str(self.start_date) \ + + encode_date_str(self.end_date) + +class BeginnerMissionConditionData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.beginner_mission_condition_id = decode_int(data, off) + off += INT_OFF + + self.beginner_mission_id = decode_int(data, off) + off += INT_OFF + + self.seat_num = decode_short(data, off) + off += SHORT_OFF + + self.mission_num = decode_short(data, off) + off += SHORT_OFF + + self.display_content, new_off = decode_str(data, off) + off += new_off + + self.display_tips, new_off = decode_str(data, off) + off += new_off + + self.condition_type = decode_byte(data, off) + off += BYTE_OFF + + self.condition_param_1, new_off = decode_str(data, off) + off += new_off + + self.condition_param_2, new_off = decode_str(data, off) + off += new_off + + self.condition_param_3, new_off = decode_str(data, off) + off += new_off + + self.required_achievement_num = decode_short(data, off) + off += SHORT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "BeginnerMissionConditionData": + ret = cls(b"\x00" * 99, 0) + ret.beginner_mission_condition_id = data['BeginnerMissionConditionId'] + ret.beginner_mission_id = data['BeginnerMissionId'] + ret.seat_num = data['SeatNum'] + ret.mission_num = data['MissionNum'] + ret.display_content = data['DisplayContent'] + ret.display_tips = data['DisplayTips'] + ret.condition_type = data['ConditionType'] + ret.condition_param_1 = data['ConditionParam1'] + ret.condition_param_2 = data['ConditionParam2'] + ret.condition_param_3 = data['ConditionParam3'] + ret.required_achievement_num = data['RequiredAchievementNum'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.beginner_mission_condition_id) \ + + encode_int(self.beginner_mission_id) \ + + encode_short(self.seat_num) \ + + encode_short(self.mission_num) \ + + encode_str(self.display_content) \ + + encode_str(self.display_tips) \ + + encode_byte(self.condition_type) \ + + encode_str(self.condition_param_1) \ + + encode_str(self.condition_param_2) \ + + encode_str(self.condition_param_3) \ + + encode_short(self.required_achievement_num) + +class BeginnerMissionRewardData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.beginner_mission_id = decode_int(data, off) + off += INT_OFF + + self.beginner_mission_condition_id = decode_int(data, off) + off += INT_OFF + + self.common_reward_type = decode_byte(data, off) + off += BYTE_OFF + + self.common_reward_id = decode_int(data, off) + off += INT_OFF + + self.common_reward_num = decode_short(data, off) + off += SHORT_OFF + + self.strength = decode_int(data, off) + off += INT_OFF + + self.property1_property_id = decode_int(data, off) + off += INT_OFF + + self.property1_value1 = decode_int(data, off) + off += INT_OFF + + self.property1_value2 = decode_int(data, off) + off += INT_OFF + + self.property2_property_id = decode_int(data, off) + off += INT_OFF + + self.property2_value1 = decode_int(data, off) + off += INT_OFF + + self.property2_value2 = decode_int(data, off) + off += INT_OFF + + self.property3_property_id = decode_int(data, off) + off += INT_OFF + + self.property3_value1 = decode_int(data, off) + off += INT_OFF + + self.property3_value2 = decode_int(data, off) + off += INT_OFF + + self.property4_property_id = decode_int(data, off) + off += INT_OFF + + self.property4_value1 = decode_int(data, off) + off += INT_OFF + + self.property4_value2 = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "BeginnerMissionRewardData": + ret = cls(b"\x00" * 99, 0) + ret.beginner_mission_id = data['BeginnerMissionId'] + ret.beginner_mission_condition_id = data['BeginnerMissionConditionId'] + ret.common_reward_type = data['CommonRewardType'] + ret.common_reward_id = data['CommonRewardId'] + ret.common_reward_num = data['CommonRewardNum'] + ret.strength = data['Strength'] + ret.property1_property_id = data['Property1PropertyId'] + ret.property1_value1 = data['Property1Value1'] + ret.property1_value2 = data['Property1Value2'] + ret.property2_property_id = data['Property2PropertyId'] + ret.property2_value1 = data['Property2Value1'] + ret.property2_value2 = data['Property2Value2'] + ret.property3_property_id = data['Property3PropertyId'] + ret.property3_value1 = data['Property3Value1'] + ret.property3_value2 = data['Property3Value2'] + ret.property4_property_id = data['Property4PropertyId'] + ret.property4_value1 = data['Property4Value1'] + ret.property4_value2 = data['Property4Value2'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.beginner_mission_id) \ + + encode_int(self.beginner_mission_condition_id) \ + + encode_byte(self.common_reward_type) \ + + encode_int(self.common_reward_id) \ + + encode_short(self.common_reward_num) \ + + encode_int(self.strength) \ + + encode_int(self.property1_property_id) \ + + encode_int(self.property1_value1) \ + + encode_int(self.property1_value2) \ + + encode_int(self.property2_property_id) \ + + encode_int(self.property2_value1) \ + + encode_int(self.property2_value2) \ + + encode_int(self.property3_property_id) \ + + encode_int(self.property3_value1) \ + + encode_int(self.property3_value2) \ + + encode_int(self.property4_property_id) \ + + encode_int(self.property4_value1) \ + + encode_int(self.property4_value2) + +class BeginnerMissionSeatConditionData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.beginner_mission_id = decode_int(data, off) + off += INT_OFF + + self.beginner_mission_seat_condition_id = decode_int(data, off) + off += INT_OFF + + self.seat_num = decode_short(data, off) + off += SHORT_OFF + + self.mission_seat_num = decode_short(data, off) + off += SHORT_OFF + + self.display_content, new_off = decode_str(data, off) + off += new_off + + self.display_tips, new_off = decode_str(data, off) + off += new_off + + self.condition_type = decode_byte(data, off) + off += BYTE_OFF + + self.condition_param_1, new_off = decode_str(data, off) + off += new_off + + self.condition_param_2, new_off = decode_str(data, off) + off += new_off + + self.condition_param_3, new_off = decode_str(data, off) + off += new_off + + self.required_achievement_num = decode_short(data, off) + off += SHORT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "BeginnerMissionSeatConditionData": + ret = cls(b"\x00" * 99, 0) + ret.beginner_mission_id = data['BeginnerMissionId'] + ret.beginner_mission_seat_condition_id = data['BeginnerMissionSeatConditionId'] + ret.seat_num = data['SeatNum'] + ret.mission_seat_num = data['MissionSeatNum'] + ret.display_content = data['DisplayContent'] + ret.display_tips = data['DisplayTips'] + ret.condition_type = data['ConditionType'] + ret.condition_param_1 = data['ConditionParam1'] + ret.condition_param_2 = data['ConditionParam2'] + ret.condition_param_3 = data['ConditionParam3'] + ret.required_achievement_num = data['RequiredAchievementNum'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.beginner_mission_id) \ + + encode_int(self.beginner_mission_seat_condition_id) \ + + encode_short(self.seat_num) \ + + encode_short(self.mission_seat_num) \ + + encode_str(self.display_content) \ + + encode_str(self.display_tips) \ + + encode_byte(self.condition_type) \ + + encode_str(self.condition_param_1) \ + + encode_str(self.condition_param_2) \ + + encode_str(self.condition_param_3) \ + + encode_short(self.required_achievement_num) + +class BeginnerMissionSeatRewardData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.beginner_mission_id = decode_int(data, off) + off += INT_OFF + + self.beginner_mission_seat_condition_id = decode_int(data, off) + off += INT_OFF + + self.common_reward_type = decode_byte(data, off) + off += BYTE_OFF + + self.common_reward_id = decode_int(data, off) + off += INT_OFF + + self.common_reward_num = decode_short(data, off) + off += SHORT_OFF + + self.strength = decode_int(data, off) + off += INT_OFF + + self.property1_property_id = decode_int(data, off) + off += INT_OFF + + self.property1_value1 = decode_int(data, off) + off += INT_OFF + + self.property1_value2 = decode_int(data, off) + off += INT_OFF + + self.property2_property_id = decode_int(data, off) + off += INT_OFF + + self.property2_value1 = decode_int(data, off) + off += INT_OFF + + self.property2_value2 = decode_int(data, off) + off += INT_OFF + + self.property3_property_id = decode_int(data, off) + off += INT_OFF + + self.property3_value1 = decode_int(data, off) + off += INT_OFF + + self.property3_value2 = decode_int(data, off) + off += INT_OFF + + self.property4_property_id = decode_int(data, off) + off += INT_OFF + + self.property4_value1 = decode_int(data, off) + off += INT_OFF + + self.property4_value2 = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "BeginnerMissionSeatRewardData": + ret = cls(b"\x00" * 99, 0) + ret.beginner_mission_id = data['BeginnerMissionId'] + ret.beginner_mission_seat_condition_id = data['BeginnerMissionSeatConditionId'] + ret.common_reward_type = data['CommonRewardType'] + ret.common_reward_id = data['CommonRewardId'] + ret.common_reward_num = data['CommonRewardNum'] + ret.strength = data['Strength'] + ret.property1_property_id = data['Property1PropertyId'] + ret.property1_value1 = data['Property1Value1'] + ret.property1_value2 = data['Property1Value2'] + ret.property2_property_id = data['Property2PropertyId'] + ret.property2_value1 = data['Property2Value1'] + ret.property2_value2 = data['Property2Value2'] + ret.property3_property_id = data['Property3PropertyId'] + ret.property3_value1 = data['Property3Value1'] + ret.property3_value2 = data['Property3Value2'] + ret.property4_property_id = data['Property4PropertyId'] + ret.property4_value1 = data['Property4Value1'] + ret.property4_value2 = data['Property4Value2'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.beginner_mission_id) \ + + encode_int(self.beginner_mission_seat_condition_id) \ + + encode_byte(self.common_reward_type) \ + + encode_int(self.common_reward_id) \ + + encode_short(self.common_reward_num) \ + + encode_int(self.strength) \ + + encode_int(self.property1_property_id) \ + + encode_int(self.property1_value1) \ + + encode_int(self.property1_value2) \ + + encode_int(self.property2_property_id) \ + + encode_int(self.property2_value1) \ + + encode_int(self.property2_value2) \ + + encode_int(self.property3_property_id) \ + + encode_int(self.property3_value1) \ + + encode_int(self.property3_value2) \ + + encode_int(self.property4_property_id) \ + + encode_int(self.property4_value1) \ + + encode_int(self.property4_value2) + +class EventItemData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.event_item_id = decode_int(data, off) + off += INT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self.flavor_text, new_off = decode_str(data, off) + off += new_off + + self.event_item_icon, new_off = decode_str(data, off) + off += new_off + + self.unit_id = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "EventItemData": + ret = cls(b"\x00" * 99, 0) + ret.event_item_id = data['EventItemId'] + ret.name = data['Name'] + ret.flavor_text = data['FlavorText'] + ret.event_item_icon = data['EventItemIcon'] + ret.unit_id = data['UnitId'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.event_item_id) \ + + encode_str(self.name) \ + + encode_str(self.flavor_text) \ + + encode_str(self.event_item_icon) \ + + encode_int(self.unit_id) + +class EventMonsterData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.event_monster_id = decode_int(data, off) + off += INT_OFF + + self.event_id = decode_int(data, off) + off += INT_OFF + + self.unit_id = decode_int(data, off) + off += INT_OFF + + self.event_item_id = decode_int(data, off) + off += INT_OFF + + self.drop_rate, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "EventMonsterData": + ret = cls(b"\x00" * 99, 0) + ret.event_monster_id = data['EventMonsterId'] + ret.event_id = data['EventId'] + ret.unit_id = data['UnitId'] + ret.event_item_id = data['EventItemId'] + ret.drop_rate = data['DropRate'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.event_monster_id) \ + + encode_int(self.event_id) \ + + encode_int(self.unit_id) \ + + encode_int(self.event_item_id) \ + + encode_str(self.drop_rate) + +class YuiMedalBonusData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.yui_medal_bonus_id = decode_int(data, off) + off += INT_OFF + + self.start_date, new_off = decode_date_str(data, off) + off += new_off + + self.end_date, new_off = decode_date_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "YuiMedalBonusData": + ret = cls(b"\x00" * 99, 0) + ret.yui_medal_bonus_id = data['YuiMedalBonusId'] + ret.start_date = data['StartDate'] + ret.end_date = data['EndDate'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.yui_medal_bonus_id) \ + + encode_date_str(self.start_date) \ + + encode_date_str(self.end_date) + +class YuiMedalBonusConditionData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.yui_medal_bonus_condition_id = decode_int(data, off) + off += INT_OFF + + self.yui_medal_bonus_id = decode_int(data, off) + off += INT_OFF + + self.target_days = decode_int(data, off) + off += INT_OFF + + self.get_num = decode_int(data, off) + off += INT_OFF + + self.loop_flag = decode_byte(data, off) + off += BYTE_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "YuiMedalBonusConditionData": + ret = cls(b"\x00" * 99, 0) + ret.yui_medal_bonus_condition_id = data['YuiMedalBonusConditionId'] + ret.yui_medal_bonus_id = data['YuiMedalBonusId'] + ret.target_days = data['TargetDays'] + ret.get_num = data['GetNum'] + ret.loop_flag = data['LoopFlag'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.yui_medal_bonus_condition_id) \ + + encode_int(self.yui_medal_bonus_id) \ + + encode_int(self.target_days) \ + + encode_int(self.get_num) \ + + encode_byte(self.loop_flag) + +class GashaMedalData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.gasha_medal_id = decode_int(data, off) + off += INT_OFF + + self.gasha_medal_type = decode_byte(data, off) + off += BYTE_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "GashaMedalData": + ret = cls(b"\x00" * 99, 0) + ret.gasha_medal_id = data['GashaMedalId'] + ret.gasha_medal_type = data['GashaMedalType'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.gasha_medal_id) \ + + encode_byte(self.gasha_medal_type) + +class GashaMedalTypeData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.gasha_medal_type = decode_int(data, off) + off += INT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self.medal_icon, new_off = decode_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "GashaMedalTypeData": + ret = cls(b"\x00" * 99, 0) + ret.gasha_medal_type = data['GashaMedalType'] + ret.name = data['Name'] + ret.medal_icon = data['MedalIcon'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.gasha_medal_type) \ + + encode_str(self.name) \ + + encode_str(self.medal_icon) + +class GashaMedalSettingData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.gasha_id = decode_int(data, off) + off += INT_OFF + + self.gasha_medal_id = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "GashaMedalSettingData": + ret = cls(b"\x00" * 99, 0) + ret.gasha_id = data['GashaId'] + ret.gasha_medal_id = data['GashaMedalId'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.gasha_id) \ + + encode_int(self.gasha_medal_id) + +class GashaMedalBonusData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.analyze_amount = decode_byte(data, off) + off += BYTE_OFF + + self.get_gasha_medal_num = decode_int(data, off) + off += INT_OFF + + self.start_date, new_off = decode_date_str(data, off) + off += new_off + + self.end_date, new_off = decode_date_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "GashaMedalBonusData": + ret = cls(b"\x00" * 99, 0) + ret.analyze_amount = data['AnalyzeAmount'] + ret.get_gasha_medal_num = data['GetGashaMedalNum'] + ret.start_date = data['StartDate'] + ret.end_date = data['EndDate'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_byte(self.analyze_amount) \ + + encode_int(self.get_gasha_medal_num) \ + + encode_date_str(self.start_date) \ + + encode_date_str(self.end_date) + +class GashaMedalShopData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.gasha_medal_shop_id = decode_int(data, off) + off += INT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self.gasha_medal_id = decode_int(data, off) + off += INT_OFF + + self.use_gasha_medal_num = decode_int(data, off) + off += INT_OFF + + self.purchase_limit = decode_short(data, off) + off += SHORT_OFF + + self.start_date, new_off = decode_date_str(data, off) + off += new_off + + self.end_date, new_off = decode_date_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "GashaMedalShopData": + ret = cls(b"\x00" * 99, 0) + ret.gasha_medal_shop_id = data['GashaMedalShopId'] + ret.name = data['Name'] + ret.gasha_medal_id = data['GashaMedalId'] + ret.use_gasha_medal_num = data['UseGashaMedalNum'] + ret.purchase_limit = data['PurchaseLimit'] + ret.start_date = data['StartDate'] + ret.end_date = data['EndDate'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.gasha_medal_shop_id) \ + + encode_str(self.name) \ + + encode_int(self.gasha_medal_id) \ + + encode_int(self.use_gasha_medal_num) \ + + encode_short(self.purchase_limit) \ + + encode_date_str(self.start_date) \ + + encode_date_str(self.end_date) + +class GashaMedalShopItemData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.gasha_medal_shop_item_id = decode_int(data, off) + off += INT_OFF + + self.gasha_medal_shop_id = decode_int(data, off) + off += INT_OFF + + self.common_reward_type = decode_byte(data, off) + off += BYTE_OFF + + self.common_reward_id = decode_int(data, off) + off += INT_OFF + + self.common_reward_num = decode_short(data, off) + off += SHORT_OFF + + self.strength = decode_int(data, off) + off += INT_OFF + + self.property1_property_id = decode_int(data, off) + off += INT_OFF + + self.property1_value1 = decode_int(data, off) + off += INT_OFF + + self.property1_value2 = decode_int(data, off) + off += INT_OFF + + self.property2_property_id = decode_int(data, off) + off += INT_OFF + + self.property2_value1 = decode_int(data, off) + off += INT_OFF + + self.property2_value2 = decode_int(data, off) + off += INT_OFF + + self.property3_property_id = decode_int(data, off) + off += INT_OFF + + self.property3_value1 = decode_int(data, off) + off += INT_OFF + + self.property3_value2 = decode_int(data, off) + off += INT_OFF + + self.property4_property_id = decode_int(data, off) + off += INT_OFF + + self.property4_value1 = decode_int(data, off) + off += INT_OFF + + self.property4_value2 = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "GashaMedalShopItemData": + ret = cls(b"\x00" * 99, 0) + ret.gasha_medal_shop_item_id = data['GashaMedalShopItemId'] + ret.gasha_medal_shop_id = data['GashaMedalShopId'] + ret.common_reward_type = data['CommonRewardType'] + ret.common_reward_id = data['CommonRewardId'] + ret.common_reward_num = data['CommonRewardNum'] + ret.strength = data['Strength'] + ret.property1_property_id = data['Property1PropertyId'] + ret.property1_value1 = data['Property1Value1'] + ret.property1_value2 = data['Property1Value2'] + ret.property2_property_id = data['Property2PropertyId'] + ret.property2_value1 = data['Property2Value1'] + ret.property2_value2 = data['Property2Value2'] + ret.property3_property_id = data['Property3PropertyId'] + ret.property3_value1 = data['Property3Value1'] + ret.property3_value2 = data['Property3Value2'] + ret.property4_property_id = data['Property4PropertyId'] + ret.property4_value1 = data['Property4Value1'] + ret.property4_value2 = data['Property4Value2'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.gasha_medal_shop_item_id) \ + + encode_int(self.gasha_medal_shop_id) \ + + encode_byte(self.common_reward_type) \ + + encode_int(self.common_reward_id) \ + + encode_short(self.common_reward_num) \ + + encode_int(self.strength) \ + + encode_int(self.property1_property_id) \ + + encode_int(self.property1_value1) \ + + encode_int(self.property1_value2) \ + + encode_int(self.property2_property_id) \ + + encode_int(self.property2_value1) \ + + encode_int(self.property2_value2) \ + + encode_int(self.property3_property_id) \ + + encode_int(self.property3_value1) \ + + encode_int(self.property3_value2) \ + + encode_int(self.property4_property_id) \ + + encode_int(self.property4_value1) \ + + encode_int(self.property4_value2) + +class ResEarnCampaignApplicationData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.res_earn_campaign_application_id = decode_int(data, off) + off += INT_OFF + + self.title, new_off = decode_str(data, off) + off += new_off + + self.open_start_date, new_off = decode_date_str(data, off) + off += new_off + + self.open_end_date, new_off = decode_date_str(data, off) + off += new_off + + self.posting_start_date, new_off = decode_date_str(data, off) + off += new_off + + self.posting_end_date, new_off = decode_date_str(data, off) + off += new_off + + self.news_id, new_off = decode_str(data, off) + off += new_off + + self.help_id = decode_int(data, off) + off += INT_OFF + + self.ad_banner_id = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "ResEarnCampaignApplicationData": + ret = cls(b"\x00" * 99, 0) + ret.res_earn_campaign_application_id = data['ResEarnCampaignApplicationId'] + ret.title = data['Title'] + ret.open_start_date = data['OpenStartDate'] + ret.open_end_date = data['OpenEndDate'] + ret.posting_start_date = data['PostingStartDate'] + ret.posting_end_date = data['PostingEndDate'] + ret.news_id = data['NewsId'] + ret.help_id = data['HelpId'] + ret.ad_banner_id = data['AdBannerId'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.res_earn_campaign_application_id) \ + + encode_str(self.title) \ + + encode_date_str(self.open_start_date) \ + + encode_date_str(self.open_end_date) \ + + encode_date_str(self.posting_start_date) \ + + encode_date_str(self.posting_end_date) \ + + encode_str(self.news_id) \ + + encode_int(self.help_id) \ + + encode_int(self.ad_banner_id) + +class ResEarnCampaignApplicationProductData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.res_earn_campaign_application_product_id = decode_int(data, off) + off += INT_OFF + + self.res_earn_campaign_application_id = decode_int(data, off) + off += INT_OFF + + self.award_name, new_off = decode_str(data, off) + off += new_off + + self.name, new_off = decode_str(data, off) + off += new_off + + self.need_application_point = decode_short(data, off) + off += SHORT_OFF + + self.winning_num = decode_short(data, off) + off += SHORT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "ResEarnCampaignApplicationProductData": + ret = cls(b"\x00" * 99, 0) + ret.res_earn_campaign_application_product_id = data['ResEarnCampaignApplicationProductId'] + ret.res_earn_campaign_application_id = data['ResEarnCampaignApplicationId'] + ret.award_name = data['AwardName'] + ret.name = data['Name'] + ret.need_application_point = data['NeedApplicationPoint'] + ret.winning_num = data['WinningNum'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.res_earn_campaign_application_product_id) \ + + encode_int(self.res_earn_campaign_application_id) \ + + encode_str(self.award_name) \ + + encode_str(self.name) \ + + encode_short(self.need_application_point) \ + + encode_short(self.winning_num) + +class ResEarnCampaignShopData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.res_earn_campaign_shop_id = decode_int(data, off) + off += INT_OFF + + self.res_earn_campaign_application_id = decode_int(data, off) + off += INT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self.selling_yui_medal = decode_short(data, off) + off += SHORT_OFF + + self.selling_col = decode_int(data, off) + off += INT_OFF + + self.selling_event_item_id = decode_int(data, off) + off += INT_OFF + + self.selling_event_item_num = decode_int(data, off) + off += INT_OFF + + self.purchase_limit = decode_short(data, off) + off += SHORT_OFF + + self.get_application_point = decode_short(data, off) + off += SHORT_OFF + + self.start_date, new_off = decode_date_str(data, off) + off += new_off + + self.end_date, new_off = decode_date_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "ResEarnCampaignShopData": + ret = cls(b"\x00" * 99, 0) + ret.res_earn_campaign_shop_id = data['ResEarnCampaignShopId'] + ret.res_earn_campaign_application_id = data['ResEarnCampaignApplicationId'] + ret.name = data['Name'] + ret.selling_yui_medal = data['SellingYuiMedal'] + ret.selling_col = data['SellingCol'] + ret.selling_event_item_id = data['SellingEventItemId'] + ret.selling_event_item_num = data['SellingEventItemNum'] + ret.purchase_limit = data['PurchaseLimit'] + ret.get_application_point = data['GetApplicationPoint'] + ret.start_date = data['StartDate'] + ret.end_date = data['EndDate'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.res_earn_campaign_shop_id) \ + + encode_int(self.res_earn_campaign_application_id) \ + + encode_str(self.name) \ + + encode_short(self.selling_yui_medal) \ + + encode_int(self.selling_col) \ + + encode_int(self.selling_event_item_id) \ + + encode_int(self.selling_event_item_num) \ + + encode_short(self.purchase_limit) \ + + encode_short(self.get_application_point) \ + + encode_date_str(self.start_date) \ + + encode_date_str(self.end_date) + +class ResEarnCampaignShopItemData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.res_earn_campaign_shop_item_id = decode_int(data, off) + off += INT_OFF + + self.res_earn_campaign_shop_id = decode_int(data, off) + off += INT_OFF + + self.common_reward_type = decode_byte(data, off) + off += BYTE_OFF + + self.common_reward_id = decode_int(data, off) + off += INT_OFF + + self.common_reward_num = decode_short(data, off) + off += SHORT_OFF + + self.strength = decode_int(data, off) + off += INT_OFF + + self.property1_property_id = decode_int(data, off) + off += INT_OFF + + self.property1_value1 = decode_int(data, off) + off += INT_OFF + + self.property1_value2 = decode_int(data, off) + off += INT_OFF + + self.property2_property_id = decode_int(data, off) + off += INT_OFF + + self.property2_value1 = decode_int(data, off) + off += INT_OFF + + self.property2_value2 = decode_int(data, off) + off += INT_OFF + + self.property3_property_id = decode_int(data, off) + off += INT_OFF + + self.property3_value1 = decode_int(data, off) + off += INT_OFF + + self.property3_value2 = decode_int(data, off) + off += INT_OFF + + self.property4_property_id = decode_int(data, off) + off += INT_OFF + + self.property4_value1 = decode_int(data, off) + off += INT_OFF + + self.property4_value2 = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "ResEarnCampaignShopItemData": + ret = cls(b"\x00" * 99, 0) + ret.res_earn_campaign_shop_item_id = data['ResEarnCampaignShopItemId'] + ret.res_earn_campaign_shop_id = data['ResEarnCampaignShopId'] + ret.common_reward_type = data['CommonRewardType'] + ret.common_reward_id = data['CommonRewardId'] + ret.common_reward_num = data['CommonRewardNum'] + ret.strength = data['Strength'] + ret.property1_property_id = data['Property1PropertyId'] + ret.property1_value1 = data['Property1Value1'] + ret.property1_value2 = data['Property1Value2'] + ret.property2_property_id = data['Property2PropertyId'] + ret.property2_value1 = data['Property2Value1'] + ret.property2_value2 = data['Property2Value2'] + ret.property3_property_id = data['Property3PropertyId'] + ret.property3_value1 = data['Property3Value1'] + ret.property3_value2 = data['Property3Value2'] + ret.property4_property_id = data['Property4PropertyId'] + ret.property4_value1 = data['Property4Value1'] + ret.property4_value2 = data['Property4Value2'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.res_earn_campaign_shop_item_id) \ + + encode_int(self.res_earn_campaign_shop_id) \ + + encode_byte(self.common_reward_type) \ + + encode_int(self.common_reward_id) \ + + encode_short(self.common_reward_num) \ + + encode_int(self.strength) \ + + encode_int(self.property1_property_id) \ + + encode_int(self.property1_value1) \ + + encode_int(self.property1_value2) \ + + encode_int(self.property2_property_id) \ + + encode_int(self.property2_value1) \ + + encode_int(self.property2_value2) \ + + encode_int(self.property3_property_id) \ + + encode_int(self.property3_value1) \ + + encode_int(self.property3_value2) \ + + encode_int(self.property4_property_id) \ + + encode_int(self.property4_value1) \ + + encode_int(self.property4_value2) + +class PayingYuiMedalBonusData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.paying_yui_medal_bonus_id = decode_int(data, off) + off += INT_OFF + + self.title, new_off = decode_str(data, off) + off += new_off + + self.open_start_date, new_off = decode_date_str(data, off) + off += new_off + + self.open_end_date, new_off = decode_date_str(data, off) + off += new_off + + self.reward_yui_medal_num = decode_int(data, off) + off += INT_OFF + + self.news_id, new_off = decode_str(data, off) + off += new_off + + self.help_id = decode_int(data, off) + off += INT_OFF + + self.ad_banner_id = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "PayingYuiMedalBonusData": + ret = cls(b"\x00" * 99, 0) + ret.paying_yui_medal_bonus_id = data['PayingYuiMedalBonusId'] + ret.title = data['Title'] + ret.open_start_date = data['OpenStartDate'] + ret.open_end_date = data['OpenEndDate'] + ret.reward_yui_medal_num = data['RewardYuiMedalNum'] + ret.news_id = data['NewsId'] + ret.help_id = data['HelpId'] + ret.ad_banner_id = data['AdBannerId'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.paying_yui_medal_bonus_id) \ + + encode_str(self.title) \ + + encode_date_str(self.open_start_date) \ + + encode_date_str(self.open_end_date) \ + + encode_int(self.reward_yui_medal_num) \ + + encode_str(self.news_id) \ + + encode_int(self.help_id) \ + + encode_int(self.ad_banner_id) + +class AcLoginBonusData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.ac_login_bonus_id = decode_int(data, off) + off += INT_OFF + + self.title, new_off = decode_str(data, off) + off += new_off + + self.reward_set_sub_id = decode_int(data, off) + off += INT_OFF + + self.open_start_date, new_off = decode_date_str(data, off) + off += new_off + + self.open_end_date, new_off = decode_date_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "AcLoginBonusData": + ret = cls(b"\x00" * 99, 0) + ret.ac_login_bonus_id = data['AcLoginBonusId'] + ret.title = data['Title'] + ret.reward_set_sub_id = data['RewardSetSubId'] + ret.open_start_date = data['OpenStartDate'] + ret.open_end_date = data['OpenEndDate'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.ac_login_bonus_id) \ + + encode_str(self.title) \ + + encode_int(self.reward_set_sub_id) \ + + encode_date_str(self.open_start_date) \ + + encode_date_str(self.open_end_date) + +class PlayCampaignData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.play_campaign_id = decode_int(data, off) + off += INT_OFF + + self.title, new_off = decode_str(data, off) + off += new_off + + self.campaign_type = decode_byte(data, off) + off += BYTE_OFF + + self.open_start_date, new_off = decode_date_str(data, off) + off += new_off + + self.open_end_date, new_off = decode_date_str(data, off) + off += new_off + + self.posting_start_date, new_off = decode_date_str(data, off) + off += new_off + + self.posting_end_date, new_off = decode_date_str(data, off) + off += new_off + + self.news_id, new_off = decode_str(data, off) + off += new_off + + self.help_id = decode_int(data, off) + off += INT_OFF + + self.ad_banner_id = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "PlayCampaignData": + ret = cls(b"\x00" * 99, 0) + ret.play_campaign_id = data['PlayCampaignId'] + ret.title = data['Title'] + ret.campaign_type = data['CampaignType'] + ret.open_start_date = data['OpenStartDate'] + ret.open_end_date = data['OpenEndDate'] + ret.posting_start_date = data['PostingStartDate'] + ret.posting_end_date = data['PostingEndDate'] + ret.news_id = data['NewsId'] + ret.help_id = data['HelpId'] + ret.ad_banner_id = data['AdBannerId'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.play_campaign_id) \ + + encode_str(self.title) \ + + encode_byte(self.campaign_type) \ + + encode_date_str(self.open_start_date) \ + + encode_date_str(self.open_end_date) \ + + encode_date_str(self.posting_start_date) \ + + encode_date_str(self.posting_end_date) \ + + encode_str(self.news_id) \ + + encode_int(self.help_id) \ + + encode_int(self.ad_banner_id) + +class PlayCampaignRewardData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.play_campaign_reward_id = decode_int(data, off) + off += INT_OFF + + self.play_campaign_id = decode_int(data, off) + off += INT_OFF + + self.product_no = decode_int(data, off) + off += INT_OFF + + self.product_name, new_off = decode_str(data, off) + off += new_off + + self.product_type = decode_byte(data, off) + off += BYTE_OFF + + self.resource_type = decode_byte(data, off) + off += BYTE_OFF + + self.required_credit_num = decode_int(data, off) + off += INT_OFF + + self.common_reward_type = decode_byte(data, off) + off += BYTE_OFF + + self.common_reward_id = decode_int(data, off) + off += INT_OFF + + self.common_reward_num = decode_short(data, off) + off += SHORT_OFF + + self.strength = decode_int(data, off) + off += INT_OFF + + self.property1_property_id = decode_int(data, off) + off += INT_OFF + + self.property1_value1 = decode_int(data, off) + off += INT_OFF + + self.property1_value2 = decode_int(data, off) + off += INT_OFF + + self.property2_property_id = decode_int(data, off) + off += INT_OFF + + self.property2_value1 = decode_int(data, off) + off += INT_OFF + + self.property2_value2 = decode_int(data, off) + off += INT_OFF + + self.property3_property_id = decode_int(data, off) + off += INT_OFF + + self.property3_value1 = decode_int(data, off) + off += INT_OFF + + self.property3_value2 = decode_int(data, off) + off += INT_OFF + + self.property4_property_id = decode_int(data, off) + off += INT_OFF + + self.property4_value1 = decode_int(data, off) + off += INT_OFF + + self.property4_value2 = decode_int(data, off) + off += INT_OFF + + self.title_id = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "PlayCampaignRewardData": + ret = cls(b"\x00" * 99, 0) + ret.play_campaign_reward_id = data['PlayCampaignRewardId'] + ret.play_campaign_id = data['PlayCampaignId'] + ret.product_no = data['ProductNo'] + ret.product_name = data['ProductName'] + ret.product_type = data['ProductType'] + ret.resource_type = data['ResourceType'] + ret.required_credit_num = data['RequiredCreditNum'] + ret.common_reward_type = data['CommonRewardType'] + ret.common_reward_id = data['CommonRewardId'] + ret.common_reward_num = data['CommonRewardNum'] + ret.strength = data['Strength'] + ret.property1_property_id = data['Property1PropertyId'] + ret.property1_value1 = data['Property1Value1'] + ret.property1_value2 = data['Property1Value2'] + ret.property2_property_id = data['Property2PropertyId'] + ret.property2_value1 = data['Property2Value1'] + ret.property2_value2 = data['Property2Value2'] + ret.property3_property_id = data['Property3PropertyId'] + ret.property3_value1 = data['Property3Value1'] + ret.property3_value2 = data['Property3Value2'] + ret.property4_property_id = data['Property4PropertyId'] + ret.property4_value1 = data['Property4Value1'] + ret.property4_value2 = data['Property4Value2'] + ret.title_id = data['TitleId'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.play_campaign_reward_id) \ + + encode_int(self.play_campaign_id) \ + + encode_int(self.product_no) \ + + encode_str(self.product_name) \ + + encode_byte(self.product_type) \ + + encode_byte(self.resource_type) \ + + encode_int(self.required_credit_num) \ + + encode_byte(self.common_reward_type) \ + + encode_int(self.common_reward_id) \ + + encode_short(self.common_reward_num) \ + + encode_int(self.strength) \ + + encode_int(self.property1_property_id) \ + + encode_int(self.property1_value1) \ + + encode_int(self.property1_value2) \ + + encode_int(self.property2_property_id) \ + + encode_int(self.property2_value1) \ + + encode_int(self.property2_value2) \ + + encode_int(self.property3_property_id) \ + + encode_int(self.property3_value1) \ + + encode_int(self.property3_value2) \ + + encode_int(self.property4_property_id) \ + + encode_int(self.property4_value1) \ + + encode_int(self.property4_value2) \ + + encode_int(self.title_id) + +class GashaFreeCampaignData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.gasha_free_campaign_id = decode_int(data, off) + off += INT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self.start_date, new_off = decode_date_str(data, off) + off += new_off + + self.end_date, new_off = decode_date_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "GashaFreeCampaignData": + ret = cls(b"\x00" * 99, 0) + ret.gasha_free_campaign_id = data['GashaFreeCampaignId'] + ret.name = data['Name'] + ret.start_date = data['StartDate'] + ret.end_date = data['EndDate'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.gasha_free_campaign_id) \ + + encode_str(self.name) \ + + encode_date_str(self.start_date) \ + + encode_date_str(self.end_date) + +class QuestDropBoostCampaignData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.quest_drop_boost_campaign_id = decode_int(data, off) + off += INT_OFF + + self.name, new_off = decode_str(data, off) + off += new_off + + self.consume_ticket_num = decode_int(data, off) + off += INT_OFF + + self.drop_magnification = decode_byte(data, off) + off += BYTE_OFF + + self.open_start_date, new_off = decode_date_str(data, off) + off += new_off + + self.open_end_date, new_off = decode_date_str(data, off) + off += new_off + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "QuestDropBoostCampaignData": + ret = cls(b"\x00" * 99, 0) + ret.quest_drop_boost_campaign_id = data['QuestDropBoostCampaignId'] + ret.name = data['Name'] + ret.consume_ticket_num = data['ConsumeTicketNum'] + ret.drop_magnification = data['DropMagnification'] + ret.open_start_date = data['OpenStartDate'] + ret.open_end_date = data['OpenEndDate'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.quest_drop_boost_campaign_id) \ + + encode_str(self.name) \ + + encode_int(self.consume_ticket_num) \ + + encode_byte(self.drop_magnification) \ + + encode_date_str(self.open_start_date) \ + + encode_date_str(self.open_end_date) + +class FirstTicketPurchaseCampaignData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.first_ticket_purchase_campaign_id = decode_int(data, off) + off += INT_OFF + + self.title, new_off = decode_str(data, off) + off += new_off + + self.open_start_date, new_off = decode_date_str(data, off) + off += new_off + + self.open_end_date, new_off = decode_date_str(data, off) + off += new_off + + self.not_target_applying_base_date, new_off = decode_date_str(data, off) + off += new_off + + self.target_ticket_purchase_id_list, new_off = decode_str(data, off) + off += new_off + + self.news_id, new_off = decode_str(data, off) + off += new_off + + self.help_id = decode_int(data, off) + off += INT_OFF + + self.ad_banner_id = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "FirstTicketPurchaseCampaignData": + ret = cls(b"\x00" * 99, 0) + ret.first_ticket_purchase_campaign_id = data['FirstTicketPurchaseCampaignId'] + ret.title = data['Title'] + ret.open_start_date = data['OpenStartDate'] + ret.open_end_date = data['OpenEndDate'] + ret.not_target_applying_base_date = data['NotTargetApplyingBaseDate'] + ret.target_ticket_purchase_id_list = data['TargetTicketPurchaseIdList'] + ret.news_id = data['NewsId'] + ret.help_id = data['HelpId'] + ret.ad_banner_id = data['AdBannerId'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.first_ticket_purchase_campaign_id) \ + + encode_str(self.title) \ + + encode_date_str(self.open_start_date) \ + + encode_date_str(self.open_end_date) \ + + encode_date_str(self.not_target_applying_base_date) \ + + encode_str(self.target_ticket_purchase_id_list) \ + + encode_str(self.news_id) \ + + encode_int(self.help_id) \ + + encode_int(self.ad_banner_id) + +class LinkedSiteRegCampaignsData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.linked_site_reg_campaign_id = decode_int(data, off) + off += INT_OFF + + self.title, new_off = decode_str(data, off) + off += new_off + + self.open_start_date, new_off = decode_date_str(data, off) + off += new_off + + self.open_end_date, new_off = decode_date_str(data, off) + off += new_off + + self.posting_start_date, new_off = decode_date_str(data, off) + off += new_off + + self.posting_end_date, new_off = decode_date_str(data, off) + off += new_off + + self.news_id, new_off = decode_str(data, off) + off += new_off + + self.help_id = decode_int(data, off) + off += INT_OFF + + self.ad_banner_id = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "LinkedSiteRegCampaignsData": + ret = cls(b"\x00" * 99, 0) + ret.linked_site_reg_campaign_id = data['LinkedSiteRegCampaignId'] + ret.title = data['Title'] + ret.open_start_date = data['OpenStartDate'] + ret.open_end_date = data['OpenEndDate'] + ret.posting_start_date = data['PostingStartDate'] + ret.posting_end_date = data['PostingEndDate'] + ret.news_id = data['NewsId'] + ret.help_id = data['HelpId'] + ret.ad_banner_id = data['AdBannerId'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.linked_site_reg_campaign_id) \ + + encode_str(self.title) \ + + encode_date_str(self.open_start_date) \ + + encode_date_str(self.open_end_date) \ + + encode_date_str(self.posting_start_date) \ + + encode_date_str(self.posting_end_date) \ + + encode_str(self.news_id) \ + + encode_int(self.help_id) \ + + encode_int(self.ad_banner_id) + +class LinkedSiteRegCampaignRewardData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.linked_site_reg_campaign_reward_id = decode_int(data, off) + off += INT_OFF + + self.linked_site_reg_campaign_id = decode_int(data, off) + off += INT_OFF + + self.common_reward_type = decode_byte(data, off) + off += BYTE_OFF + + self.common_reward_id = decode_int(data, off) + off += INT_OFF + + self.common_reward_num = decode_short(data, off) + off += SHORT_OFF + + self.strength = decode_int(data, off) + off += INT_OFF + + self.property1_property_id = decode_int(data, off) + off += INT_OFF + + self.property1_value1 = decode_int(data, off) + off += INT_OFF + + self.property1_value2 = decode_int(data, off) + off += INT_OFF + + self.property2_property_id = decode_int(data, off) + off += INT_OFF + + self.property2_value1 = decode_int(data, off) + off += INT_OFF + + self.property2_value2 = decode_int(data, off) + off += INT_OFF + + self.property3_property_id = decode_int(data, off) + off += INT_OFF + + self.property3_value1 = decode_int(data, off) + off += INT_OFF + + self.property3_value2 = decode_int(data, off) + off += INT_OFF + + self.property4_property_id = decode_int(data, off) + off += INT_OFF + + self.property4_value1 = decode_int(data, off) + off += INT_OFF + + self.property4_value2 = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, data: Dict) -> "LinkedSiteRegCampaignRewardData": + ret = cls(b"\x00" * 99, 0) + ret.linked_site_reg_campaign_reward_id = data['LinkedSiteRegCampaignRewardId'] + ret.linked_site_reg_campaign_id = data['LinkedSiteRegCampaignId'] + ret.common_reward_type = data['CommonRewardType'] + ret.common_reward_id = data['CommonRewardId'] + ret.common_reward_num = data['CommonRewardNum'] + ret.strength = data['Strength'] + ret.property1_property_id = data['Property1PropertyId'] + ret.property1_value1 = data['Property1Value1'] + ret.property1_value2 = data['Property1Value2'] + ret.property2_property_id = data['Property2PropertyId'] + ret.property2_value1 = data['Property2Value1'] + ret.property2_value2 = data['Property2Value2'] + ret.property3_property_id = data['Property3PropertyId'] + ret.property3_value1 = data['Property3Value1'] + ret.property3_value2 = data['Property3Value2'] + ret.property4_property_id = data['Property4PropertyId'] + ret.property4_value1 = data['Property4Value1'] + ret.property4_value2 = data['Property4Value2'] + return ret + + def make(self) -> bytes: + return super().make() \ + + encode_int(self.linked_site_reg_campaign_reward_id) \ + + encode_int(self.linked_site_reg_campaign_id) \ + + encode_byte(self.common_reward_type) \ + + encode_int(self.common_reward_id) \ + + encode_short(self.common_reward_num) \ + + encode_int(self.strength) \ + + encode_int(self.property1_property_id) \ + + encode_int(self.property1_value1) \ + + encode_int(self.property1_value2) \ + + encode_int(self.property2_property_id) \ + + encode_int(self.property2_value1) \ + + encode_int(self.property2_value2) \ + + encode_int(self.property3_property_id) \ + + encode_int(self.property3_value1) \ + + encode_int(self.property3_value2) \ + + encode_int(self.property4_property_id) \ + + encode_int(self.property4_value1) \ + + encode_int(self.property4_value2) + +class EpisodeAppendUserData(BaseHelper): + def __init__(self, data: bytes, offset: int): + off = offset + self.user_episode_append_id, new_off = decode_str(data, off) + off += new_off + + self.user_id, new_off = decode_str(data, off) + off += new_off + + self.episode_append_id = decode_int(data, off) + off += INT_OFF + + self.own_num = decode_int(data, off) + off += INT_OFF + + self._sz = off - offset + + @classmethod + def from_args(cls, episode_id: int = 0, user_id: int = 0, episode_append_id: int = 0, own_num: int = 99) -> "EpisodeAppendUserData": + ret = cls(b"\x00" * 996, 0) + ret.user_episode_append_id = episode_id + ret.user_id = user_id + ret.episode_append_id = episode_append_id + ret.own_num = own_num + + def make(self) -> bytes: + return super().make() \ + + encode_str(self.user_episode_append_id) \ + + encode_str(self.user_id) \ + + encode_int(self.episode_append_id) \ + + encode_int(self.own_num) diff --git a/titles/sao/index.py b/titles/sao/index.py index 1f68c43..8a775c6 100644 --- a/titles/sao/index.py +++ b/titles/sao/index.py @@ -1,17 +1,20 @@ from typing import Tuple, Dict, List from starlette.requests import Request -from starlette.responses import Response +from starlette.responses import Response, PlainTextResponse, FileResponse from starlette.routing import Route import yaml import logging, coloredlogs from logging.handlers import TimedRotatingFileHandler from os import path from Crypto.Cipher import Blowfish -from hashlib import md5 +from Crypto.Hash import MD5 import secrets +import traceback +import sys -from core import CoreConfig, Utils +from core import CoreConfig from core.title import BaseServlet +from core.utils import Utils from titles.sao.config import SaoConfig from titles.sao.const import SaoConstants from titles.sao.base import SaoBase @@ -55,13 +58,15 @@ class SaoServlet(BaseServlet): self.static_hash = None if self.game_cfg.hash.verify_hash: - self.static_hash = md5(self.game_cfg.hash.hash_base.encode()).digest() # Greate hashing guys, really validates the data - + self.static_hash = MD5.new(self.game_cfg.hash.hash_base.encode()).digest() # Greate hashing guys, really validates the data + def get_routes(self) -> List[Route]: return [ - Route("/{datecode:int}/proto/if/{category:str}/{endpoint:str}", self.render_POST, methods=['POST']) + Route("/{datecode:int}/proto/if/{category:str}/{endpoint:str}", self.render_POST, methods=['POST']), + Route("/saoresource/{resource_type:str}/{resource_id:int}/{endpoint:str}", self.handle_resource), + Route("/system_status.php", self.render_system_status), ] - + @classmethod def is_game_enabled(cls, game_code: str, core_cfg: CoreConfig, cfg_dir: str) -> bool: game_cfg = SaoConfig() @@ -75,29 +80,25 @@ class SaoServlet(BaseServlet): return False return True - - def get_allnet_info(self, game_code: str, game_ver: int, keychip: str) -> Tuple[str, str]: - port_ssl = Utils.get_title_port_ssl(self.core_cfg) - port_normal = Utils.get_title_port(self.core_cfg) - proto = "http" - port = f":{port_normal}" if port_normal != 80 else "" + def get_allnet_info(self, game_id: str, int_ver: int, serial: str) -> Tuple[str, str]: + if self.core_cfg.server.is_using_proxy: + return (f"https://{self.core_cfg.server.hostname}/", "") - if self.game_cfg.server.use_https: - proto = "https" - port = f":{port_ssl}" if port_ssl != 443 else "" + return (f"http://{self.core_cfg.server.hostname}:{Utils.get_title_port(self.core_cfg)}/", "") - return (f"{proto}://{self.core_cfg.server.hostname}{port}/", "") - - - def get_mucha_info(self, core_cfg: CoreConfig, cfg_dir: str) -> Tuple[bool, str]: + def get_mucha_info(self, core_cfg: CoreConfig, cfg_dir: str) -> Tuple[bool, List[str], List[str]]: if not self.game_cfg.server.enable: return (False, [], []) return (True, SaoConstants.GAME_CDS, SaoConstants.NETID_PREFIX) + async def render_system_status(self, request: Request) -> bytes: + return PlainTextResponse("open") + async def render_POST(self, request: Request) -> bytes: - endpoint = request.path_params.get('endpoint', '') + endpoint = request.path_params['endpoint'] + ip = Utils.get_ip_addr(request) iv = b"" req_raw = await request.body() @@ -122,10 +123,23 @@ class SaoServlet(BaseServlet): else: req_data = req_raw[40:] - handler = getattr(self.base, f"handle_{cmd_str}", self.base.handle_noop) - self.logger.info(f"{endpoint} - {cmd_str} request") - self.logger.debug(f"Request: {req_raw.hex()}") - resp = await handler(req_header, req_data) + self.logger.debug(f"{endpoint} ({cmd_str}) Request from {ip}: {req_raw.hex()}") + handler = getattr(self.base, f"handle_{cmd_str}", None) + if handler is None: + self.logger.info(f"Using Generic handler for {endpoint}") + handler = self.base.handle_noop + + try: + resp = await handler(req_header, req_data, ip) + + except Exception as e: + self.logger.error(f"Error handling {endpoint} - {e}") + tp, val, tb = sys.exc_info() + traceback.print_exception(tp, val, tb, limit=5) + with open("{0}/{1}.log".format(self.core_cfg.server.log_dir, "sao"), "a") as f: + traceback.print_exception(tp, val, tb, limit=5, file=f) + + resp = SaoNoopResponse(req_header.cmd + 1).make() if resp is None: resp = SaoNoopResponse(req_header.cmd + 1).make() @@ -138,7 +152,7 @@ class SaoServlet(BaseServlet): else: self.logger.error(f"Unknown response type {type(resp)}") - return Response() + return SaoNoopResponse(req_header.cmd + 1).make() self.logger.debug(f"Response: {resp.hex()}") @@ -154,6 +168,17 @@ class SaoServlet(BaseServlet): tmp = struct.pack("!I", crypt_data_len) # does it want the length of the encrypted response?? resp = resp[:20] + tmp + iv + data_crypt self.logger.debug(f"Encrypted Response: {resp.hex()}") - - - return Response(resp, media_type="text/html; charset=utf-8") \ No newline at end of file + + return Response(resp) + + async def handle_resource(self, request: Request) -> bytes: + # TODO: better guard against path traversal attacks + resource_type = request.path_params['resource_type'].replace(".\\", "").replace("..\\", "") + resource_id = request.path_params['resource_id'] + endpoint = request.path_params['endpoint'].replace(".\\", "").replace("..\\", "") + req_ip = Utils.get_ip_addr(request) + + self.logger.debug(f"{req_ip} requested {resource_type} id {resource_id} {endpoint}") + if path.exists(f"./titles/sao/data/{resource_type}/{resource_id}/{endpoint}"): + return FileResponse(f"./titles/sao/data/{resource_type}/{resource_id}/{endpoint}") + return Response(status_code=404) diff --git a/titles/sao/read.py b/titles/sao/read.py index 92aad8c..6299bc4 100644 --- a/titles/sao/read.py +++ b/titles/sao/read.py @@ -1,24 +1,16 @@ -from typing import Optional, Dict, List -from os import walk, path -import urllib import csv +from os import path +from typing import Optional, Dict, List -from read import BaseReader from core.config import CoreConfig -from titles.sao.database import SaoData -from titles.sao.const import SaoConstants +from read import BaseReader +from .database import SaoData +from .const import SaoConstants class SaoReader(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) + 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 = SaoData(config) try: @@ -28,225 +20,207 @@ class SaoReader(BaseReader): except IndexError: self.logger.error(f"Invalid project SAO version {version}") exit(1) - + async def read(self) -> None: if path.exists(self.bin_dir): await self.read_csv(f"{self.bin_dir}") else: self.logger.warn("Directory not found, nothing to import") - + + def load_csv_file(self, file: str) -> List[Dict]: + ret = [] + try: + fullPath = self.bin_dir + "/" if not self.bin_dir.endswith("/") else "" + fullPath += file + with open(fullPath, encoding="UTF-8") as fp: + reader = csv.DictReader(fp) + for row in reader: + tmp = {} + + fkey = list(row.keys())[0] + new_fkey = fkey.replace("// ", "") + fval = row[fkey] + row.pop(fkey) + row[new_fkey] = fval + + for k,v in row.items(): + if v == "-1": + row[k] = None + elif v.isdigit(): + row[k] = int(v) + elif v.isdecimal(): + row[k] = float(v) + elif v == "True": + row[k] = True + elif v == "False": + row[k] = False + ret.append(row) + + except Exception as e: + self.logger.warning(f"Couldn't read csv file {fullPath}, skipping - {e}") + + return ret async def read_csv(self, bin_dir: str) -> None: self.logger.info(f"Read csv from {bin_dir}") self.logger.info("Now reading QuestScene.csv") - try: - fullPath = bin_dir + "/QuestScene.csv" - with open(fullPath, encoding="UTF-8") as fp: - reader = csv.DictReader(fp) - for row in reader: - questSceneId = row["QuestSceneId"] - sortNo = row["SortNo"] - name = row["Name"] - enabled = True + reader = self.load_csv_file("QuestScene.csv") + if reader: + for row in reader: + self.logger.info(f"Adding quest {row['QuestSceneId']}") + await self.data.static.put_quest(row) - self.logger.info(f"Added quest {questSceneId} | Name: {name}") - - try: - await self.data.static.put_quest( - questSceneId, - 0, - sortNo, - name, - enabled - ) - except Exception as err: - self.logger.error(err) - except Exception: - self.logger.warning(f"Couldn't read csv file in {self.bin_dir}, skipping") - - self.logger.info("Now reading HeroLog.csv") - try: - fullPath = bin_dir + "/HeroLog.csv" - with open(fullPath, encoding="UTF-8") as fp: - reader = csv.DictReader(fp) - for row in reader: - heroLogId = row["HeroLogId"] - name = row["Name"] - nickname = row["Nickname"] - rarity = row["Rarity"] - skillTableSubId = row["SkillTableSubId"] - awakeningExp = row["AwakeningExp"] - flavorText = row["FlavorText"] - enabled = True + self.logger.info("Now reading Property.csv") + reader = self.load_csv_file("Property.csv") + if reader: + for row in reader: + self.logger.info(f"Adding property {row['PropertyId']}") + await self.data.static.put_property(row) - self.logger.info(f"Added hero {heroLogId} | Name: {name}") - - try: - await self.data.static.put_hero( - 0, - heroLogId, - name, - nickname, - rarity, - skillTableSubId, - awakeningExp, - flavorText, - enabled - ) - except Exception as err: - self.logger.error(err) - except Exception: - self.logger.warning(f"Couldn't read csv file in {self.bin_dir}, skipping") - self.logger.info("Now reading Equipment.csv") - try: - fullPath = bin_dir + "/Equipment.csv" - with open(fullPath, encoding="UTF-8") as fp: - reader = csv.DictReader(fp) - for row in reader: - equipmentId = row["EquipmentId"] - equipmentType = row["EquipmentType"] - weaponTypeId = row["WeaponTypeId"] - name = row["Name"] - rarity = row["Rarity"] - flavorText = row["FlavorText"] - enabled = True + reader = self.load_csv_file("Equipment.csv") + if reader: + for row in reader: + self.logger.info(f"Adding equipment {row['EquipmentId']}") + await self.data.static.put_equipment(row) - self.logger.info(f"Added equipment {equipmentId} | Name: {name}") - - try: - await self.data.static.put_equipment( - 0, - equipmentId, - name, - equipmentType, - weaponTypeId, - rarity, - flavorText, - enabled - ) - except Exception as err: - self.logger.error(err) - except Exception: - self.logger.warning(f"Couldn't read csv file in {self.bin_dir}, skipping") + self.logger.info("Now reading Skill.csv") + reader = self.load_csv_file("Skill.csv") + if reader: + for row in reader: + self.logger.info(f"Adding skill {row['SkillId']}") + await self.data.static.put_skill(row) + + self.logger.info("Now reading SkillTable.csv") + reader = self.load_csv_file("SkillTable.csv") + if reader: + for row in reader: + self.logger.info(f"Adding skill table {row['SkillId']} | SubId: {row['SkillTableSubId']}") + await self.data.static.put_skill_table(row['SkillId'], row['SkillTableSubId'], row['Level'], row['AwakeningId'], row['SkillTableId']) + + self.logger.info("Now reading HeroLog.csv") + reader = self.load_csv_file("HeroLog.csv") + if reader: + for row in reader: + self.logger.info(f"Adding hero {row['HeroLogId']}") + await self.data.static.put_hero(row) self.logger.info("Now reading Item.csv") - try: - fullPath = bin_dir + "/Item.csv" - with open(fullPath, encoding="UTF-8") as fp: - reader = csv.DictReader(fp) - for row in reader: - itemId = row["ItemId"] - itemTypeId = row["ItemTypeId"] - name = row["Name"] - rarity = row["Rarity"] - flavorText = row["FlavorText"] - enabled = True + reader = self.load_csv_file("Item.csv") + if reader: + for row in reader: + self.logger.info(f"Adding item {row['ItemId']}") + await self.data.static.put_item(row) - self.logger.info(f"Added item {itemId} | Name: {name}") - - try: - await self.data.static.put_item( - 0, - itemId, - name, - itemTypeId, - rarity, - flavorText, - enabled - ) - except Exception as err: - self.logger.error(err) - except Exception: - self.logger.warning(f"Couldn't read csv file in {self.bin_dir}, skipping") - self.logger.info("Now reading SupportLog.csv") - try: - fullPath = bin_dir + "/SupportLog.csv" - with open(fullPath, encoding="UTF-8") as fp: - reader = csv.DictReader(fp) - for row in reader: - supportLogId = row["SupportLogId"] - charaId = row["CharaId"] - name = row["Name"] - rarity = row["Rarity"] - salePrice = row["SalePrice"] - skillName = row["SkillName"] - enabled = True + reader = self.load_csv_file("SupportLog.csv") + if reader: + for row in reader: + supportLogId = row["SupportLogId"] + charaId = row["CharaId"] + name = row["Name"] + rarity = row["Rarity"] + salePrice = row["SalePrice"] + skillName = row["SkillName"] + enabled = True + + self.logger.info(f"Adding support log {supportLogId}") + await self.data.static.put_support_log( + 0, + supportLogId, + charaId, + name, + rarity, + salePrice, + skillName, + enabled + ) - self.logger.info(f"Added support log {supportLogId} | Name: {name}") - - try: - await self.data.static.put_support_log( - 0, - supportLogId, - charaId, - name, - rarity, - salePrice, - skillName, - enabled - ) - except Exception as err: - self.logger.error(err) - except Exception: - self.logger.warning(f"Couldn't read csv file in {self.bin_dir}, skipping") - self.logger.info("Now reading Title.csv") - try: - fullPath = bin_dir + "/Title.csv" - with open(fullPath, encoding="UTF-8") as fp: - reader = csv.DictReader(fp) - for row in reader: - titleId = row["TitleId"] - displayName = row["DisplayName"] - requirement = row["Requirement"] - rank = row["Rank"] - imageFilePath = row["ImageFilePath"] - enabled = True + reader = self.load_csv_file("Title.csv") + if reader: + for row in reader: + titleId = row["TitleId"] + displayName = row["DisplayName"] + requirement = row["Requirement"] + rank = row["Rank"] + imageFilePath = row["ImageFilePath"] + enabled = True - self.logger.info(f"Added title {titleId} | Name: {displayName}") - - if len(titleId) > 5: - try: - await self.data.static.put_title( - 0, - titleId, - displayName, - requirement, - rank, - imageFilePath, - enabled - ) - except Exception as err: - self.logger.error(err) - elif len(titleId) < 6: # current server code cannot have multiple lengths for the id - continue - except Exception: - self.logger.warning(f"Couldn't read csv file in {self.bin_dir}, skipping") + self.logger.info(f"Adding title {titleId}") + await self.data.static.put_title( + 0, + titleId, + displayName, + requirement, + rank, + imageFilePath, + enabled + ) - self.logger.info("Now reading RareDropTable.csv") - try: - fullPath = bin_dir + "/RareDropTable.csv" - with open(fullPath, encoding="UTF-8") as fp: - reader = csv.DictReader(fp) - for row in reader: - questRareDropId = row["QuestRareDropId"] - commonRewardId = row["CommonRewardId"] - enabled = True + self.logger.info("Now reading QuestRareDrop.csv") + reader = self.load_csv_file("QuestRareDrop.csv") + if reader: + for row in reader: + questRareDropId = row["QuestRareDropId"] + commonRewardId = row["CommonRewardId"] + enabled = True - self.logger.info(f"Added rare drop {questRareDropId} | Reward: {commonRewardId}") - - try: - await self.data.static.put_rare_drop( - 0, - questRareDropId, - commonRewardId, - enabled - ) - except Exception as err: - self.logger.error(err) - except Exception: - self.logger.warning(f"Couldn't read csv file in {self.bin_dir}, skipping") + self.logger.info(f"Adding rare drop {questRareDropId} | Reward: {commonRewardId}") + await self.data.static.put_rare_drop( + 0, + questRareDropId, + commonRewardId, + enabled + ) + + self.logger.info("Now reading RewardTable.csv") + reader = self.load_csv_file("RewardTable.csv") + if reader: + for row in reader: + self.logger.info(f"Adding reward table {row['RewardTableId']} | Sub-ID: {row['RewardTableSubId']} | Reward {row['CommonRewardId']}") + await self.data.static.put_reward_table(row) + + self.logger.info("Now reading ExBonusTable.csv") + reader = self.load_csv_file("ExBonusTable.csv") + if reader: + for row in reader: + self.logger.info(f"Adding ex bonus {row['ExBonusTableId']} | Sub-ID: {row['ExBonusTableSubId']} | Reward {row['CommonRewardId']}") + await self.data.static.put_ex_bonus(row) + + self.logger.info("Now reading PlayerTraceTable.csv") + reader = self.load_csv_file("PlayerTraceTable.csv") + if reader: + for row in reader: + self.logger.info(f"Adding trace table {row['PlayerTraceTableId']} | Sub-ID: {row['PlayerTraceTableSubId']} | Reward {row['CommonRewardId']}") + await self.data.static.put_player_trace(row) + + self.logger.info("Now reading Episode.csv") + reader = self.load_csv_file("Episode.csv") + if reader: + for row in reader: + self.logger.info(f"Adding episode {row['EpisodeId']}") + await self.data.static.put_episode(row) + + self.logger.info("Now reading TrialTower.csv") + reader = self.load_csv_file("TrialTower.csv") + if reader: + for row in reader: + self.logger.info(f"Adding tower {row['TrialTowerId']}") + await self.data.static.put_tower(row) + + self.logger.info("Now reading ExTowerQuests.csv") + reader = self.load_csv_file("ExTowerQuests.csv") + if reader: + for row in reader: + self.logger.info(f"Adding ex tower {row['ExTowerQuestId']}") + await self.data.static.put_ex_tower(row) + + self.logger.info("Now reading SideQuest.csv") + reader = self.load_csv_file("SideQuest.csv") + if reader: + for row in reader: + self.logger.info(f"Adding side quest {row['SideQuestId']}") + await self.data.static.put_side_quest(row) diff --git a/titles/sao/schema/item.py b/titles/sao/schema/item.py index 332e135..abadca5 100644 --- a/titles/sao/schema/item.py +++ b/titles/sao/schema/item.py @@ -1,6 +1,6 @@ from typing import Optional, Dict, List from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_, case -from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON +from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, BOOLEAN, INTEGER, BIGINT from sqlalchemy.schema import ForeignKey from sqlalchemy.sql import func, select, update, delete from sqlalchemy.engine import Row @@ -17,15 +17,30 @@ equipment_data = Table( ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False, ), - Column("equipment_id", Integer, nullable=False), + Column("equipment_id", BIGINT, ForeignKey("sao_static_equipment_list.EquipmentId", ondelete="cascade", onupdate="cascade"), nullable=False), Column("enhancement_value", Integer, nullable=False), Column("enhancement_exp", Integer, nullable=False), Column("awakening_exp", Integer, nullable=False), Column("awakening_stage", Integer, nullable=False), Column("possible_awakening_flag", Integer, nullable=False), + Column("is_shop_purchase", BOOLEAN, nullable=False, server_default="0"), + Column("is_protect", BOOLEAN, nullable=False, server_default="0"), + Column("property1_property_id", BIGINT, ForeignKey("sao_static_property.PropertyId", ondelete="cascade", onupdate="cascade"), nullable=False, server_default="2"), + Column("property1_value1", INTEGER, nullable=False, server_default="0"), + Column("property1_value2", INTEGER, nullable=False, server_default="0"), + Column("property2_property_id", BIGINT, ForeignKey("sao_static_property.PropertyId", ondelete="cascade", onupdate="cascade"), nullable=False, server_default="2"), + Column("property2_value1", INTEGER, nullable=False, server_default="0"), + Column("property2_value2", INTEGER, nullable=False, server_default="0"), + Column("property3_property_id", BIGINT, ForeignKey("sao_static_property.PropertyId", ondelete="cascade", onupdate="cascade"), nullable=False, server_default="2"), + Column("property3_value1", INTEGER, nullable=False, server_default="0"), + Column("property3_value2", INTEGER, nullable=False, server_default="0"), + Column("property4_property_id", BIGINT, ForeignKey("sao_static_property.PropertyId", ondelete="cascade", onupdate="cascade"), nullable=False, server_default="2"), + Column("property4_value1", INTEGER, nullable=False, server_default="0"), + Column("property4_value2", INTEGER, nullable=False, server_default="0"), + Column("converted_card_num", INTEGER, nullable=False, server_default="0"), Column("get_date", TIMESTAMP, nullable=False, server_default=func.now()), UniqueConstraint("user", "equipment_id", name="sao_equipment_data_uk"), - mysql_charset="utf8mb4", + mysql_charset="utf8mb4" ) item_data = Table( @@ -40,7 +55,7 @@ item_data = Table( Column("item_id", Integer, nullable=False), Column("get_date", TIMESTAMP, nullable=False, server_default=func.now()), UniqueConstraint("user", "item_id", name="sao_item_data_uk"), - mysql_charset="utf8mb4", + mysql_charset="utf8mb4" ) hero_log_data = Table( @@ -52,19 +67,38 @@ hero_log_data = Table( ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False, ), - Column("user_hero_log_id", Integer, nullable=False), + Column("hero_log_id", BIGINT, ForeignKey("sao_static_hero_list.HeroLogId", ondelete="cascade", onupdate="cascade"), nullable=False), Column("log_level", Integer, nullable=False), Column("log_exp", Integer, nullable=False), - Column("main_weapon", Integer, nullable=False), - Column("sub_equipment", Integer, nullable=False), - Column("skill_slot1_skill_id", Integer, nullable=False), - Column("skill_slot2_skill_id", Integer, nullable=False), - Column("skill_slot3_skill_id", Integer, nullable=False), - Column("skill_slot4_skill_id", Integer, nullable=False), - Column("skill_slot5_skill_id", Integer, nullable=False), + Column("main_weapon", BIGINT, ForeignKey("sao_equipment_data.id", ondelete="set null", onupdate="set null")), + Column("sub_equipment", BIGINT, ForeignKey("sao_equipment_data.id", ondelete="set null", onupdate="set null")), + Column("skill_slot1_skill_id", BIGINT, ForeignKey("sao_static_skill.SkillId", ondelete="set null", onupdate="set null")), + Column("skill_slot2_skill_id", BIGINT, ForeignKey("sao_static_skill.SkillId", ondelete="set null", onupdate="set null")), + Column("skill_slot3_skill_id", BIGINT, ForeignKey("sao_static_skill.SkillId", ondelete="set null", onupdate="set null")), + Column("skill_slot4_skill_id", BIGINT, ForeignKey("sao_static_skill.SkillId", ondelete="set null", onupdate="set null")), + Column("skill_slot5_skill_id", BIGINT, ForeignKey("sao_static_skill.SkillId", ondelete="set null", onupdate="set null")), + Column("max_level_extend_num", INTEGER, nullable=False, server_default="0"), + Column("is_awakenable", BOOLEAN, nullable=False, server_default="0"), + Column("awakening_stage", INTEGER, nullable=False, server_default="0"), + Column("awakening_exp", INTEGER, nullable=False, server_default="0"), + Column("is_shop_purchase", BOOLEAN, nullable=False, server_default="0"), + Column("is_protect", BOOLEAN, nullable=False, server_default="0"), + Column("property1_property_id", BIGINT, ForeignKey("sao_static_property.PropertyId", ondelete="cascade", onupdate="cascade"), nullable=False, server_default="2"), + Column("property1_value1", INTEGER, nullable=False, server_default="0"), + Column("property1_value2", INTEGER, nullable=False, server_default="0"), + Column("property2_property_id", BIGINT, ForeignKey("sao_static_property.PropertyId", ondelete="cascade", onupdate="cascade"), nullable=False, server_default="2"), + Column("property2_value1", INTEGER, nullable=False, server_default="0"), + Column("property2_value2", INTEGER, nullable=False, server_default="0"), + Column("property3_property_id", BIGINT, ForeignKey("sao_static_property.PropertyId", ondelete="cascade", onupdate="cascade"), nullable=False, server_default="2"), + Column("property3_value1", INTEGER, nullable=False, server_default="0"), + Column("property3_value2", INTEGER, nullable=False, server_default="0"), + Column("property4_property_id", BIGINT, ForeignKey("sao_static_property.PropertyId", ondelete="cascade", onupdate="cascade"), nullable=False, server_default="2"), + Column("property4_value1", INTEGER, nullable=False, server_default="0"), + Column("property4_value2", INTEGER, nullable=False, server_default="0"), + Column("converted_card_num", INTEGER, nullable=False, server_default="0"), Column("get_date", TIMESTAMP, nullable=False, server_default=func.now()), - UniqueConstraint("user", "user_hero_log_id", name="sao_hero_log_data_uk"), - mysql_charset="utf8mb4", + UniqueConstraint("user", "hero_log_id", name="sao_hero_log_data_uk"), + mysql_charset="utf8mb4" ) hero_party = Table( @@ -77,11 +111,11 @@ hero_party = Table( nullable=False, ), Column("user_party_team_id", Integer, nullable=False), - Column("user_hero_log_id_1", Integer, nullable=False), - Column("user_hero_log_id_2", Integer, nullable=False), - Column("user_hero_log_id_3", Integer, nullable=False), + Column("user_hero_log_id_1", Integer, ForeignKey("sao_hero_log_data.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("user_hero_log_id_2", Integer, ForeignKey("sao_hero_log_data.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("user_hero_log_id_3", Integer, ForeignKey("sao_hero_log_data.id", ondelete="cascade", onupdate="cascade"), nullable=False), UniqueConstraint("user", "user_party_team_id", name="sao_hero_party_uk"), - mysql_charset="utf8mb4", + mysql_charset="utf8mb4" ) quest = Table( @@ -93,15 +127,28 @@ quest = Table( ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False, ), - Column("episode_id", Integer, nullable=False), + Column("quest_type", INTEGER, nullable=False, server_default="1"), + Column("quest_scene_id", BIGINT, ForeignKey("sao_static_quest.QuestSceneId", ondelete="cascade", onupdate="cascade"), nullable=False), Column("quest_clear_flag", Boolean, nullable=False), Column("clear_time", Integer, nullable=False), Column("combo_num", Integer, nullable=False), Column("total_damage", Integer, nullable=False), Column("concurrent_destroying_num", Integer, nullable=False), Column("play_date", TIMESTAMP, nullable=False, server_default=func.now()), - UniqueConstraint("user", "episode_id", name="sao_player_quest_uk"), - mysql_charset="utf8mb4", + UniqueConstraint("user", "quest_scene_id", name="sao_player_quest_uk"), + mysql_charset="utf8mb4" +) + +ex_bonus = Table( + "sao_player_ex_bonus", + metadata, + Column("id", BIGINT, primary_key=True, nullable=False), + Column("user", INTEGER, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("quest_scene_id", BIGINT, ForeignKey("sao_static_quest.QuestSceneId", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("ex_bonus_table_id", BIGINT, ForeignKey("sao_static_ex_bonus.ExBonusTableId", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("quest_clear_flag", BOOLEAN, nullable=False, server_default="0"), + UniqueConstraint("user", "quest_scene_id", "ex_bonus_table_id", name="sao_player_ex_bonus_uk"), + mysql_charset="utf8mb4" ) sessions = Table( @@ -114,12 +161,12 @@ sessions = Table( nullable=False, ), Column("user_party_team_id", Integer, nullable=False), - Column("episode_id", Integer, nullable=False), + Column("episode_id", Integer, nullable=False), # TODO: Change to quest scene id Column("play_mode", Integer, nullable=False), Column("quest_drop_boost_apply_flag", Integer, nullable=False), Column("play_date", TIMESTAMP, nullable=False, server_default=func.now()), UniqueConstraint("user", "user_party_team_id", "play_date", name="sao_play_sessions_uk"), - mysql_charset="utf8mb4", + mysql_charset="utf8mb4" ) end_sessions = Table( @@ -135,7 +182,7 @@ end_sessions = Table( Column("play_result_flag", Boolean, nullable=False), Column("reward_data", JSON, nullable=True), Column("play_date", TIMESTAMP, nullable=False, server_default=func.now()), - mysql_charset="utf8mb4", + mysql_charset="utf8mb4" ) class SaoItemData(BaseData): @@ -148,13 +195,17 @@ class SaoItemData(BaseData): quest_drop_boost_apply_flag=quest_drop_boost_apply_flag ) - conflict = sql.on_duplicate_key_update(user=user_id) + conflict = sql.on_duplicate_key_update( + user_party_team_id=user_party_team_id, + episode_id=episode_id, + play_mode=play_mode, + quest_drop_boost_apply_flag=quest_drop_boost_apply_flag + ) result = await self.execute(conflict) - if result is None: - self.logger.error(f"Failed to create SAO session for user {user_id}!") - return None - return result.lastrowid + if result: + return result.inserted_primary_key['id'] + self.logger.error(f"Failed to create SAO session for user {user_id}!") async def create_end_session(self, user_id: int, quest_id: int, play_result_flag: bool, reward_data: JSON) -> Optional[int]: sql = insert(end_sessions).values( @@ -164,13 +215,16 @@ class SaoItemData(BaseData): reward_data=reward_data, ) - conflict = sql.on_duplicate_key_update(user=user_id) + conflict = sql.on_duplicate_key_update( + play_result_flag=play_result_flag, + reward_data=reward_data + ) result = await self.execute(conflict) - if result is None: - self.logger.error(f"Failed to create SAO end session for user {user_id}!") - return None - return result.lastrowid + if result: + return result.inserted_primary_key['id'] + + self.logger.error(f"Failed to create SAO end session for user {user_id}!") async def put_item(self, user_id: int, item_id: int) -> Optional[int]: sql = insert(item_data).values( @@ -178,51 +232,93 @@ class SaoItemData(BaseData): item_id=item_id, ) - conflict = sql.on_duplicate_key_update( - item_id=item_id, - ) + conflict = sql.on_duplicate_key_update(item_id=item_id) result = await self.execute(conflict) - if result is None: - self.logger.error( - f"{__name__} failed to insert item! user: {user_id}, item_id: {item_id}" - ) - return None - - return result.lastrowid + if result: + return result.inserted_primary_key['id'] + self.logger.error(f"Failed to insert item! user: {user_id}, item_id: {item_id}") - async def put_equipment_data(self, user_id: int, equipment_id: int, enhancement_value: int, enhancement_exp: int, awakening_exp: int, awakening_stage: int, possible_awakening_flag: int) -> Optional[int]: + async def put_equipment(self, user_id: int, equipment_id: int) -> Optional[int]: sql = insert(equipment_data).values( user=user_id, equipment_id=equipment_id, - enhancement_value=enhancement_value, - enhancement_exp=enhancement_exp, - awakening_exp=awakening_exp, - awakening_stage=awakening_stage, - possible_awakening_flag=possible_awakening_flag, + enhancement_value=1, + enhancement_exp=200, + awakening_exp=0, + awakening_stage=0, + possible_awakening_flag=0, ) - conflict = sql.on_duplicate_key_update( - enhancement_value=enhancement_value, - enhancement_exp=enhancement_exp, - awakening_exp=awakening_exp, - awakening_stage=awakening_stage, - possible_awakening_flag=possible_awakening_flag, - ) + conflict = sql.on_duplicate_key_update(equipment_id=equipment_id) result = await self.execute(conflict) - if result is None: - self.logger.error( - f"{__name__} failed to insert equipment! user: {user_id}, equipment_id: {equipment_id}" - ) - return None + if result: + return result.inserted_primary_key['id'] + self.logger.error(f"Failed to insert equipment! user: {user_id}, equipment_id: {equipment_id}") + + async def put_ex_bonus(self, user_id: int, quest_scene_id: int, ex_bonus_id: int, clear: bool = False) -> Optional[int]: + sql = insert(ex_bonus).values( + user=user_id, + quest_scene_id=quest_scene_id, + ex_bonus_table_id=ex_bonus_id, + quest_clear_flag=clear, + ) - return result.lastrowid + conflict = sql.on_duplicate_key_update(quest_clear_flag=clear) + result = await self.execute(conflict) + if result: + return result.inserted_primary_key['id'] + self.logger.error(f"Failed to insert ex bonus status! user: {user_id}, quest_scene_id: {quest_scene_id}, ex_bonus_id: {ex_bonus_id}, clear: {clear}") + + async def add_equipment_enhancement_exp(self, user_weapon_id: int, enhancement_exp: int) -> None: + result = await self.execute( + equipment_data.update(equipment_data.c.id == user_weapon_id) + .values(enhancement_exp=equipment_data.c.enhancement_exp + enhancement_exp) + ) + if not result: + self.logger.error(f"Failed to give equipment {user_weapon_id} {enhancement_exp} xp!") + + async def get_equipment_enhancement_exp(self, user_weapon_id: int) -> Optional[int]: + result = await self.execute(select(equipment_data.c.enhancement_exp).where(equipment_data.c.id==user_weapon_id)) + if result: + row = result.fetchone() + if row: + return row['enhancement_exp'] + return 0 + self.logger.error(f"Failed to get equipment {user_weapon_id} xp!") + + async def set_equipment_enhancement_value(self, user_equip_id: int, enhancement_val: int) -> None: + result = await self.execute(equipment_data.update(equipment_data.c.id == user_equip_id).values(enhancement_value=enhancement_val)) + if result is None: + self.logger.error(f"Failed to set equipment {user_equip_id} level to {enhancement_val}!") + + async def add_hero_xp(self, user_hero_log_id: int, add_xp: int) -> Optional[int]: + result = await self.execute( + hero_log_data.update(hero_log_data.c.id == user_hero_log_id) + .values(log_exp=hero_log_data.c.log_exp + add_xp) + ) + if not result: + self.logger.error(f"Failed to give hero {user_hero_log_id} {add_xp} xp!") + + async def get_hero_xp(self, user_hero_log_id: int) -> Optional[int]: + result = await self.execute(select(hero_log_data.c.log_exp).where(hero_log_data.c.id==user_hero_log_id)) + if result: + row = result.fetchone() + if row: + return row['log_exp'] + return 0 + self.logger.error(f"Failed to get hero xp for {user_hero_log_id}") + + async def set_hero_level(self, user_hero_log_id: int, new_level: int): + result = await self.execute(hero_log_data.update(hero_log_data.c.id == user_hero_log_id).values(log_level=new_level)) + if result is None: + self.logger.error(f"Failed to set hero {user_hero_log_id} level to {new_level}!") async def put_hero_log(self, user_id: int, user_hero_log_id: int, log_level: int, log_exp: int, main_weapon: int, sub_equipment: int, skill_slot1_skill_id: int, skill_slot2_skill_id: int, skill_slot3_skill_id: int, skill_slot4_skill_id: int, skill_slot5_skill_id: int) -> Optional[int]: sql = insert(hero_log_data).values( user=user_id, - user_hero_log_id=user_hero_log_id, + hero_log_id=user_hero_log_id, log_level=log_level, log_exp=log_exp, main_weapon=main_weapon, @@ -243,17 +339,36 @@ class SaoItemData(BaseData): skill_slot2_skill_id=skill_slot2_skill_id, skill_slot3_skill_id=skill_slot3_skill_id, skill_slot4_skill_id=skill_slot4_skill_id, - skill_slot5_skill_id=skill_slot5_skill_id, + skill_slot5_skill_id=skill_slot5_skill_id ) result = await self.execute(conflict) + if result: + return result.inserted_primary_key['id'] + self.logger.error(f"Failed to insert hero! user: {user_id}, user_hero_log_id: {user_hero_log_id}") + + async def set_user_hero_weapons(self, user_hero_id: int, main_weapon: int, sub_weapon: int) -> None: + sql = hero_log_data.update(hero_log_data.c.id == user_hero_id).values( + main_weapon=main_weapon, + sub_equipment=sub_weapon, + ) + result = await self.execute(sql) if result is None: - self.logger.error( - f"{__name__} failed to insert hero! user: {user_id}, user_hero_log_id: {user_hero_log_id}" - ) + self.logger.error(f"Failed to update user hero {user_hero_id} weapons {main_weapon}/{sub_weapon}") return None - return result.lastrowid + async def set_user_hero_skills(self, user_hero_id: int, skill1: int, skill2: int, skill3: int, skill4: int, skill5: int) -> None: + sql = hero_log_data.update(hero_log_data.c.id == user_hero_id).values( + skill_slot1_skill_id = skill1, + skill_slot2_skill_id = skill2, + skill_slot3_skill_id = skill3, + skill_slot4_skill_id = skill4, + skill_slot5_skill_id = skill5, + ) + result = await self.execute(sql) + if result is None: + self.logger.error(f"Failed to update user hero {user_hero_id} skills {skill1}/{skill2}/{skill3}/{skill4}/{skill5}") + return None async def put_hero_party(self, user_id: int, user_party_team_id: int, user_hero_log_id_1: int, user_hero_log_id_2: int, user_hero_log_id_3: int) -> Optional[int]: sql = insert(hero_party).values( @@ -267,22 +382,19 @@ class SaoItemData(BaseData): conflict = sql.on_duplicate_key_update( user_hero_log_id_1=user_hero_log_id_1, user_hero_log_id_2=user_hero_log_id_2, - user_hero_log_id_3=user_hero_log_id_3, + user_hero_log_id_3=user_hero_log_id_3 ) result = await self.execute(conflict) - if result is None: - self.logger.error( - f"{__name__} failed to insert hero party! user: {user_id}, user_party_team_id: {user_party_team_id}" - ) - return None + if result: + return result.inserted_primary_key['id'] + self.logger.error(f"Failed to insert hero party! user: {user_id}, user_party_team_id: {user_party_team_id}") - return result.lastrowid - - async def put_player_quest(self, user_id: int, episode_id: int, quest_clear_flag: bool, clear_time: int, combo_num: int, total_damage: int, concurrent_destroying_num: int) -> Optional[int]: + async def put_player_quest(self, user_id: int, quest_type: int, quest_scene_id: int, quest_clear_flag: bool, clear_time: int, combo_num: int, total_damage: int, concurrent_destroying_num: int) -> Optional[int]: sql = insert(quest).values( user=user_id, - episode_id=episode_id, + quest_type=quest_type, + quest_scene_id=quest_scene_id, quest_clear_flag=quest_clear_flag, clear_time=clear_time, combo_num=combo_num, @@ -299,13 +411,9 @@ class SaoItemData(BaseData): ) result = await self.execute(conflict) - if result is None: - self.logger.error( - f"{__name__} failed to insert quest! user: {user_id}, episode_id: {episode_id}" - ) - return None - - return result.lastrowid + if result: + return result.inserted_primary_key['id'] + self.logger.error(f"Failed to insert quest! user: {user_id}, quest_scene_id: {quest_scene_id}") async def get_user_equipment(self, user_id: int, equipment_id: int) -> Optional[Dict]: sql = equipment_data.select(equipment_data.c.user == user_id and equipment_data.c.equipment_id == equipment_id) @@ -315,6 +423,14 @@ class SaoItemData(BaseData): return None return result.fetchone() + async def get_user_equipment_by_id(self, equipment_user_id: int) -> Optional[Row]: + sql = equipment_data.select(equipment_data.c.id == equipment_user_id) + + result = await self.execute(sql) + if result is None: + return None + return result.fetchone() + async def get_user_equipments( self, user_id: int ) -> Optional[List[Row]]: @@ -349,6 +465,38 @@ class SaoItemData(BaseData): return None return result.fetchall() + async def get_player_ex_bonus_status(self, user_id: int) -> Optional[List[Row]]: + sql = equipment_data.select(ex_bonus.c.user == user_id) + + result = await self.execute(sql) + if result is None: + return None + return result.fetchall() + + async def get_player_ex_bonus_by_quest(self, user_id: int, quest_scene_id) -> Optional[List[Row]]: + sql = ex_bonus.select(and_(ex_bonus.c.user == user_id, ex_bonus.c.quest_scene_id == quest_scene_id)) + + result = await self.execute(sql) + if result is None: + return None + return result.fetchall() + + async def get_user_item_by_id( + self, user_item_id: int + ) -> Optional[Row]: + sql = item_data.select(item_data.c.id == user_item_id) + + result = await self.execute(sql) + if result is None: + return None + return result.fetchone() + + async def get_user_hero_by_id(self, user_hero_id: int) -> Optional[Row]: + result = await self.execute(hero_log_data.select(hero_log_data.c.id == user_hero_id)) + if result is None: + return None + return result.fetchone() + async def get_hero_log( self, user_id: int, user_hero_log_id: int = None ) -> Optional[List[Row]]: @@ -358,7 +506,7 @@ class SaoItemData(BaseData): sql = hero_log_data.select( and_( hero_log_data.c.user == user_id, - hero_log_data.c.user_hero_log_id == user_hero_log_id if user_hero_log_id is not None else True, + hero_log_data.c.hero_log_id == user_hero_log_id if user_hero_log_id is not None else True, ) ) @@ -366,6 +514,12 @@ class SaoItemData(BaseData): if result is None: return None return result.fetchone() + + async def get_hero_log_by_id(self, user_hero_log_id: int) -> Optional[Row]: + result = await self.execute(hero_log_data.select(hero_log_data.c.id == user_hero_log_id)) + if result is None: + return None + return result.fetchone() async def get_hero_logs( self, user_id: int @@ -373,17 +527,17 @@ class SaoItemData(BaseData): """ A catch-all hero lookup given a profile """ - sql = hero_log_data.select( - and_( - hero_log_data.c.user == user_id, - ) - ) - - result = await self.execute(sql) + result = await self.execute(hero_log_data.select(hero_log_data.c.user == user_id)) if result is None: return None return result.fetchall() + async def get_hero_party_by_id(self, party_id: int) -> Optional[Row]: + result = await self.execute(hero_party.select(hero_party.c.id == party_id)) + if result is None: + return None + return result.fetchone() + async def get_hero_party( self, user_id: int, user_party_team_id: int = None ) -> Optional[List[Row]]: @@ -397,18 +551,18 @@ class SaoItemData(BaseData): result = await self.execute(sql) if result is None: return None - return result.fetchone() + return result.fetchall() async def get_quest_log( - self, user_id: int, episode_id: int = None - ) -> Optional[List[Row]]: + self, user_id: int, quest_scene_id: int + ) -> Optional[Row]: """ - A catch-all quest lookup given a profile and episode_id + A catch-all quest lookup given a profile and quest_scene_id """ sql = quest.select( and_( quest.c.user == user_id, - quest.c.episode_id == episode_id if episode_id is not None else True, + quest.c.quest_scene_id == quest_scene_id ) ) @@ -420,16 +574,7 @@ class SaoItemData(BaseData): async def get_quest_logs( self, user_id: int ) -> Optional[List[Row]]: - """ - A catch-all quest lookup given a profile - """ - sql = quest.select( - and_( - quest.c.user == user_id, - ) - ) - - result = await self.execute(sql) + result = await self.execute(quest.select(quest.c.user == user_id)) if result is None: return None return result.fetchall() @@ -437,13 +582,7 @@ class SaoItemData(BaseData): async def get_session( self, user_id: int = None ) -> Optional[List[Row]]: - sql = sessions.select( - and_( - sessions.c.user == user_id, - ) - ).order_by( - sessions.c.play_date.asc() - ) + sql = sessions.select(sessions.c.user == user_id).order_by(sessions.c.play_date.desc()) result = await self.execute(sql) if result is None: @@ -453,54 +592,36 @@ class SaoItemData(BaseData): async def get_end_session( self, user_id: int = None ) -> Optional[List[Row]]: - sql = end_sessions.select( - and_( - end_sessions.c.user == user_id, - ) - ).order_by( - end_sessions.c.play_date.desc() - ) + sql = end_sessions.select(end_sessions.c.user == user_id).order_by(end_sessions.c.play_date.desc()) result = await self.execute(sql) if result is None: return None return result.fetchone() - - async def remove_hero_log(self, user_id: int, user_hero_log_id: int) -> None: - sql = hero_log_data.delete( - and_( - hero_log_data.c.user == user_id, - hero_log_data.c.user_hero_log_id == user_hero_log_id, - ) - ) + + async def remove_end_session(self, end_id: int) -> None: + result = await self.execute(end_sessions.delete(end_sessions.c.id == end_id)) + if result is None: + self.logger.error(f"Failed to delete end session {end_id}") + + async def remove_hero_log(self, user_hero_log_id: int) -> None: + sql = hero_log_data.delete(hero_log_data.c.id == user_hero_log_id) result = await self.execute(sql) - if result is None: - self.logger.error( - f"{__name__} failed to remove hero log! profile: {user_id}, user_hero_log_id: {user_hero_log_id}" - ) - return None + if not result: + self.logger.error(f"Failed to remove hero log id: {user_hero_log_id}") - async def remove_equipment(self, user_id: int, equipment_id: int) -> None: - sql = equipment_data.delete( - and_(equipment_data.c.user == user_id, equipment_data.c.equipment_id == equipment_id) - ) + async def remove_equipment(self, equipment_id: int) -> None: + sql = equipment_data.delete(equipment_data.c.id == equipment_id) result = await self.execute(sql) - if result is None: - self.logger.error( - f"{__name__} failed to remove equipment! profile: {user_id}, equipment_id: {equipment_id}" - ) - return None + if not result: + self.logger.error(f"Failed to remove equipment id {equipment_id}") - async def remove_item(self, user_id: int, item_id: int) -> None: - sql = item_data.delete( - and_(item_data.c.user == user_id, item_data.c.item_id == item_id) - ) + async def remove_item(self, user_item_id: int) -> None: + sql = item_data.delete(item_data.c.id == user_item_id) result = await self.execute(sql) - if result is None: - self.logger.error( - f"{__name__} failed to remove item! profile: {user_id}, item_id: {item_id}" - ) - return None \ No newline at end of file + if not result: + self.logger.error(f"Failed to remove item {user_item_id}!") + diff --git a/titles/sao/schema/profile.py b/titles/sao/schema/profile.py index 27d284d..4a6df93 100644 --- a/titles/sao/schema/profile.py +++ b/titles/sao/schema/profile.py @@ -1,13 +1,12 @@ -from typing import Optional, Dict, List +from typing import Optional, Tuple, List from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_, case -from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON +from sqlalchemy.types import Integer, String, BOOLEAN, INTEGER, BIGINT, VARCHAR, TIMESTAMP from sqlalchemy.schema import ForeignKey -from sqlalchemy.sql import func, select, update, delete +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 ..const import SaoConstants profile = Table( "sao_profile", @@ -24,9 +23,70 @@ profile = Table( Column("rank_num", Integer, server_default="1"), Column("rank_exp", Integer, server_default="0"), Column("own_col", Integer, server_default="0"), - Column("own_vp", Integer, server_default="300"), + Column("own_vp", Integer, server_default="0"), Column("own_yui_medal", Integer, server_default="0"), Column("setting_title_id", Integer, server_default="20005"), + Column("my_shop", INTEGER), + Column("fav_hero", INTEGER, ForeignKey("sao_hero_log_data.id", ondelete="set null", onupdate="cascade")), + Column("when_register", TIMESTAMP, server_default=func.now()), + Column("last_login_date", TIMESTAMP), + Column("last_yui_medal_date", TIMESTAMP), + Column("last_bonus_yui_medal_date", TIMESTAMP), + Column("last_comeback_date", TIMESTAMP), + Column("last_login_bonus_date", TIMESTAMP), + Column("ad_confirm_date", TIMESTAMP), + Column("login_ct", INTEGER, server_default="0"), + mysql_charset="utf8mb4" +) + +beginner_mission = Table( + "sao_player_beginner_mission", + metadata, + Column("id", BIGINT, primary_key=True, nullable=False), + Column("user", INTEGER, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False, unique=True), + Column("beginner_mission_id", INTEGER, nullable=False), + Column("condition_id", INTEGER, nullable=False), + Column("is_seat", BOOLEAN, nullable=False, server_default="0"), + Column("achievement_num", INTEGER, nullable=False), + Column("complete_flag", BOOLEAN, nullable=False, server_default="0"), + Column("complete_date", TIMESTAMP), + Column("reward_received_flag", BOOLEAN, nullable=False, server_default="0"), + Column("reward_received_date", TIMESTAMP), + UniqueConstraint("user", "condition_id", name="sao_player_beginner_mission_uk"), + mysql_charset="utf8mb4" +) + +resource_card = Table( + "sao_player_resource_card", + metadata, + Column("id", BIGINT, primary_key=True, nullable=False), + Column("user", INTEGER, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("common_reward_type", INTEGER, nullable=False), + Column("common_reward_id", INTEGER, nullable=False), + Column("holographic_flag", BOOLEAN, nullable=False, server_default="0"), + Column("serial", VARCHAR(20), unique=True), + mysql_charset="utf8mb4" +) + +hero_card = Table( + "sao_player_hero_card", + metadata, + Column("id", BIGINT, primary_key=True, nullable=False), + Column("user", INTEGER, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("user_hero_id", INTEGER, ForeignKey("sao_hero_log_data.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("holographic_flag", BOOLEAN, nullable=False, server_default="0"), + Column("serial", VARCHAR(20), unique=True), + mysql_charset="utf8mb4" +) + +tutorial = Table( + "sao_player_tutorial", + metadata, + Column("id", BIGINT, primary_key=True, nullable=False), + Column("user", INTEGER, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("tutorial_byte", INTEGER, nullable=False), + UniqueConstraint("user", "tutorial_byte", name="sao_player_tutorial_uk"), + mysql_charset="utf8mb4" ) class SaoProfileData(BaseData): @@ -35,10 +95,81 @@ class SaoProfileData(BaseData): conflict = sql.on_duplicate_key_update(user=user_id) result = await self.execute(conflict) + if result: + return result.inserted_primary_key['id'] + self.logger.error("Failed to create SAO profile!") + + async def set_my_shop(self, user_id: int, store_id: int): + result = await self.execute(profile.update(profile.c.user == user_id).values(my_shop = store_id)) if result is None: - self.logger.error(f"Failed to create SAO profile for user {user_id}!") - return None - return result.lastrowid + self.logger.error(f"Failed to set my shop for user {user_id} to {store_id}!") + + async def user_login(self, user_id: int) -> Optional[Row]: + sql = profile.update(profile.c.user == user_id).values( + login_ct=profile.c.login_ct + 1, + last_login_date=func.now() + ) + result = await self.execute(sql) + if result: + return result.last_updated_params() + self.logger.error(f"Failed to create log in user {user_id}!") + + async def update_yui_medal_date(self, user_id: int) -> None: + sql = profile.update(profile.c.user == user_id).values( + last_yui_medal_date=func.now() + ) + result = await self.execute(sql) + if result is None: + self.logger.error(f"Failed to update user {user_id} yui medal date!") + + async def add_yui_medals(self, user_id: int, num_medals: int = 1): + sql = profile.update(profile.c.user == user_id).values( + own_yui_medal=profile.c.own_yui_medal + num_medals + ) + result = await self.execute(sql) + if result is None: + self.logger.error(f"Failed to give user {user_id} {num_medals} yui medals!") + + async def add_col(self, user_id: int, num_col: int) -> None: + sql = profile.update(profile.c.user == user_id).values( + own_col=profile.c.own_col + num_col + ) + result = await self.execute(sql) + if not result: + self.logger.error(f"Failed to give user {user_id} {num_col} Col!") + + async def add_vp(self, user_id: int, num_vp: int) -> None: + sql = profile.update(profile.c.user == user_id).values( + own_vp=profile.c.own_vp + num_vp + ) + result = await self.execute(sql) + if not result: + self.logger.error(f"Failed to give user {user_id} {num_vp} VP!") + + async def add_exp(self, user_id: int, xp_ammount: int) -> Optional[int]: + sql = profile.update(profile.c.user == user_id).values( + rank_exp=profile.c.rank_exp + xp_ammount + ) + result = await self.execute(sql) + if not result: + self.logger.error(f"Failed to give user {user_id} {xp_ammount} xp!") + + async def get_exp(self, user_id: int) -> Optional[int]: + result = await self.execute(select(profile.c.rank_exp).where(profile.c.user==user_id)) + if result: + row = result.fetchone() + if row: + return row['rank_exp'] + return 0 + self.logger.error(f"Failed to query rank xp for user {user_id}") + + async def set_level(self, user_id: int, level: int): + sql = profile.update(profile.c.user == user_id).values( + rank_num=level + ) + result = await self.execute(sql) + if result is None: + self.logger.error(f"Failed to set user {user_id} level to {level}!") async def put_profile(self, user_id: int, user_type: int, nick_name: str, rank_num: int, rank_exp: int, own_col: int, own_vp: int, own_yui_medal: int, setting_title_id: int) -> Optional[int]: sql = insert(profile).values( @@ -63,17 +194,118 @@ class SaoProfileData(BaseData): ) result = await self.execute(conflict) - if result is None: - self.logger.error( - f"{__name__} failed to insert profile! user: {user_id}" - ) - return None - - return result.lastrowid + if result: + return result.inserted_primary_key['id'] + self.logger.error(f"Failed to insert profile! user: {user_id}") async def get_profile(self, user_id: int) -> Optional[Row]: sql = profile.select(profile.c.user == user_id) result = await self.execute(sql) if result is None: return None - return result.fetchone() \ No newline at end of file + return result.fetchone() + + async def set_profile_name(self, user_id: int, new_name: str) -> None: + sql = profile.update(profile.c.user == user_id).values( + nick_name=new_name + ) + result = await self.execute(sql) + if result is None: + self.logger.error(f"Failed to update nickname {new_name} for user {user_id}") + + async def add_tutorial_byte(self, user_id: int, tutorial_byte: int) -> None: + sql = insert(tutorial).values( + user = user_id, + tutorial_byte = tutorial_byte + ) + + conflict = sql.on_duplicate_key_update(tutorial_byte = tutorial_byte) + result = await self.execute(conflict) + if result is None: + self.logger.error(f"Failed to add tutorial byte {tutorial_byte} to user {user_id}") + + async def get_tutorial_bytes(self, user_id: int) -> Optional[List[Row]]: + sql = tutorial.select(tutorial.c.user == user_id) + + result = await self.execute(sql) + if result is None: + return None + return result.fetchall() + + async def put_hero_card(self, user_id: int, serial: str, user_hero_id: int, is_holo: bool) -> Optional[int]: + sql = insert(hero_card).values( + user=user_id, + user_hero_id=user_hero_id, + holographic_flag=is_holo, + serial=serial + ) + + conflict = sql.on_duplicate_key_update( + holographic_flag=is_holo, + serial=serial + ) + result = await self.execute(conflict) + if result: + return result.inserted_primary_key['id'] + self.logger.error(f"Failed to insert card {serial} for user {user_id} as hero {user_hero_id}") + + async def get_hero_card(self, serial: str) -> Optional[Row]: + result = await self.execute(hero_card.select(hero_card.c.serial == serial)) + if result is None: + return None + return result.fetchone() + + async def put_resource_card(self, user_id: int, serial: str, reward_type: int, reward_id: int, is_holo: bool) -> Optional[int]: + sql = insert(resource_card).values( + user=user_id, + common_reward_type=reward_type, + common_reward_id=reward_id, + holographic_flag=is_holo, + serial=serial + ) + + conflict = sql.on_duplicate_key_update( + holographic_flag=is_holo, + serial=serial + ) + result = await self.execute(conflict) + if result: + return result.inserted_primary_key['id'] + self.logger.error(f"Failed to insert card {serial} for user {user_id} as resource {reward_id}") + + async def get_resource_card(self, serial: str) -> Optional[int]: + result = await self.execute(resource_card.select(resource_card.c.serial == serial)) + if result is None: + return None + return result.fetchone() + + async def update_beginner_mission_date(self, user_id: int) -> None: + sql = profile.update(profile.c.user == user_id).values( + ad_confirm_date=func.now() + ) + result = await self.execute(sql) + if result is None: + self.logger.error(f"Failed to update user {user_id} yui medal date!") + + async def put_beginner_mission(self, user_id: int, beginner_mission_id: int, condition_id: int, is_seat: bool, achievement_num: int) -> Optional[int]: + pass + + async def complete_beginner_mission(self, user_id: int, condition_id: int) -> None: + pass + + async def reward_beginner_mission(self, user_id: int, condition_id: int) -> None: + pass + + async def get_beginner_missions(self, user_id: int) -> Optional[List[Row]]: + pass + + async def get_beginner_missions_by_mission_id(self, user_id: int, beginner_mission_id: int) -> Optional[List[Row]]: + pass + + async def get_beginner_mission(self, user_id: int, condition_id: int) -> Optional[Row]: + pass + + async def set_title(self, user_id: int, title_id: int) -> None: + result = await self.execute(profile.update(profile.c.user == user_id).values(setting_title_id=title_id)) + if not result: + self.logger.error(f"Failed to set user {user_id} title to {title_id}") diff --git a/titles/sao/schema/static.py b/titles/sao/schema/static.py index 8b5b1a1..7f4f679 100644 --- a/titles/sao/schema/static.py +++ b/titles/sao/schema/static.py @@ -1,81 +1,193 @@ -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 typing import Dict, List, Optional, Union +from sqlalchemy import Table, Column, UniqueConstraint, ForeignKey +from sqlalchemy.types import Integer, String, BIGINT, Boolean, INTEGER, VARCHAR, BOOLEAN, DECIMAL 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 +from core.data import cached quest = Table( "sao_static_quest", metadata, - Column("id", Integer, primary_key=True, nullable=False), - Column("version", Integer), - Column("questSceneId", Integer), - Column("sortNo", Integer), - Column("name", String(255)), - Column("enabled", Boolean), - UniqueConstraint( - "version", "questSceneId", name="sao_static_quest_uk" - ), - mysql_charset="utf8mb4", + Column("QuestSceneId", BIGINT, primary_key=True, nullable=False), + Column("SortNo", INTEGER, nullable=False), + Column("Tutorial", BOOLEAN, nullable=False), + Column("ColRate", DECIMAL, nullable=False), + Column("LimitDefault", INTEGER, nullable=False), + Column("LimitResurrection", INTEGER, nullable=False), + Column("RewardTableSubId", INTEGER, nullable=False), + Column("PlayerTraceTableSubId", INTEGER, nullable=False), + Column("SuccessPlayerExp", INTEGER, nullable=False), + Column("FailedPlayerExp", INTEGER, nullable=False), + Column("PairExpRate", INTEGER, nullable=False), + Column("TrioExpRate", INTEGER, nullable=False), + Column("SingleRewardVp", INTEGER, nullable=False), + Column("PairRewardVp", INTEGER, nullable=False), + Column("TrioRewardVp", INTEGER, nullable=False), + mysql_charset="utf8mb4" ) -hero = Table( - "sao_static_hero_list", +prop = Table( + "sao_static_property", metadata, - Column("id", Integer, primary_key=True, nullable=False), - Column("version", Integer), - Column("heroLogId", Integer), - Column("name", String(255)), - Column("nickname", String(255)), - Column("rarity", Integer), - Column("skillTableSubId", Integer), - Column("awakeningExp", Integer), - Column("flavorText", String(255)), - Column("enabled", Boolean), - UniqueConstraint( - "version", "heroLogId", name="sao_static_hero_list_uk" - ), - mysql_charset="utf8mb4", + Column("PropertyId", BIGINT, primary_key=True, nullable=False), + Column("PropertyTargetType", INTEGER, nullable=False), + Column("PropertyName", VARCHAR(255), nullable=False), + Column("PropertyName_en", VARCHAR(255)), + Column("PropertyNameFormat", VARCHAR(255), nullable=False), + Column("PropertyNameFormat_en", VARCHAR(255)), + Column("PropertyTypeId", INTEGER, nullable=False), + Column("Value1Min", INTEGER, nullable=False), + Column("Value1Max", INTEGER, nullable=False), + Column("Value2Min", INTEGER, nullable=False), + Column("Value2Max", INTEGER, nullable=False), + mysql_charset="utf8mb4" +) + +player_trace = Table( + "sao_static_trace_table", + metadata, + Column("PlayerTraceTableId", BIGINT, primary_key=True, nullable=False), + Column("PlayerTraceTableSubId", INTEGER, nullable=False), + Column("CommonRewardType", INTEGER, nullable=False), + Column("CommonRewardId", INTEGER, nullable=False), + Column("CommonRewardNum", INTEGER, nullable=False), + Column("Rate", INTEGER, nullable=False), + mysql_charset="utf8mb4" +) + +skill = Table( + "sao_static_skill", + metadata, + Column("SkillId", BIGINT, nullable=False, primary_key=True), + Column("WeaponTypeId", INTEGER, nullable=False), + Column("Name", VARCHAR(255), nullable=False), + Column("Name_en", VARCHAR(255)), + Column("Attack", BOOLEAN, nullable=False), + Column("Passive", BOOLEAN, nullable=False), + Column("Pet", BOOLEAN, nullable=False), + Column("Level", INTEGER, nullable=False), + Column("SkillCondition", INTEGER, nullable=False), + Column("CoolTime", INTEGER, nullable=False), + Column("SkillIcon", VARCHAR(255), nullable=False), + Column("FriendSkillIcon", VARCHAR(255), nullable=False), + Column("InfoText", VARCHAR(255), nullable=False), + Column("InfoText_en", VARCHAR(255)), + mysql_charset="utf8mb4" +) + +skill_table = Table( + "sao_static_skill_table", + metadata, + Column("SkillTableId", BIGINT, nullable=False, primary_key=True), + Column("SkillId", BIGINT, ForeignKey("sao_static_skill.SkillId", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("SkillTableSubId", INTEGER, nullable=False), + Column("LevelObtained", INTEGER, nullable=False), # Level + Column("AwakeningId", INTEGER, nullable=False), + mysql_charset="utf8mb4" ) equipment = Table( "sao_static_equipment_list", metadata, - Column("id", Integer, primary_key=True, nullable=False), - Column("version", Integer), - Column("equipmentId", Integer), - Column("equipmentType", Integer), - Column("weaponTypeId", Integer), - Column("name", String(255)), - Column("rarity", Integer), - Column("flavorText", String(255)), - Column("enabled", Boolean), - UniqueConstraint( - "version", "equipmentId", name="sao_static_equipment_list_uk" - ), - mysql_charset="utf8mb4", + Column("EquipmentId", BIGINT, primary_key=True, nullable=False), + Column("EquipmentType", INTEGER, nullable=False), + Column("WeaponTypeId", INTEGER, nullable=False), + Column("Name", VARCHAR(255), nullable=False), + Column("Name_en", VARCHAR(255)), + Column("Rarity", INTEGER, nullable=False), + Column("Power", INTEGER, nullable=False), + Column("StrengthIncrement", INTEGER, nullable=False), + Column("SkillCondition", INTEGER, nullable=False), + Column("Property1PropertyId", BIGINT, ForeignKey("sao_static_property.PropertyId", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("Property1Value1", INTEGER, nullable=False), + Column("Property1Value2", INTEGER, nullable=False), + Column("Property2PropertyId", BIGINT, ForeignKey("sao_static_property.PropertyId", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("Property2Value1", INTEGER, nullable=False), + Column("Property2Value2", INTEGER, nullable=False), + Column("Property3PropertyId", BIGINT, ForeignKey("sao_static_property.PropertyId", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("Property3Value1", INTEGER, nullable=False), + Column("Property3Value2", INTEGER, nullable=False), + Column("Property4PropertyId", BIGINT, ForeignKey("sao_static_property.PropertyId", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("Property4Value1", INTEGER, nullable=False), + Column("Property4Value2", INTEGER, nullable=False), + Column("SalePrice", INTEGER, nullable=False), + Column("CompositionExp", INTEGER, nullable=False), + Column("AwakeningExp", INTEGER, nullable=False), + Column("FlavorText", VARCHAR(255), nullable=False), + Column("FlavorText_en", VARCHAR(255)), + mysql_charset="utf8mb4" +) + +hero = Table( + "sao_static_hero_list", + metadata, + Column("HeroLogId", BIGINT, primary_key=True, nullable=False), + Column("CharaId", INTEGER, nullable=False), + Column("Name", VARCHAR(255), nullable=False), + Column("Nickname", VARCHAR(255), nullable=False), + Column("Name_en", VARCHAR(255)), + Column("Nickname_en", VARCHAR(255)), + Column("Rarity", INTEGER, nullable=False), + Column("WeaponTypeId", INTEGER, nullable=False), + Column("HeroLogRoleId", INTEGER, nullable=False), + Column("CostumeTypeId", INTEGER, nullable=False), + Column("UnitId", INTEGER, nullable=False), + Column("DefaultEquipmentId1", BIGINT, ForeignKey("sao_static_equipment_list.EquipmentId", ondelete="cascade", onupdate="cascade")), + Column("DefaultEquipmentId2", BIGINT, ForeignKey("sao_static_equipment_list.EquipmentId", ondelete="cascade", onupdate="cascade")), + Column("SkillTableSubId", INTEGER, nullable=False), + Column("HpMin", INTEGER, nullable=False), + Column("HpMax", INTEGER, nullable=False), + Column("StrMin", INTEGER, nullable=False), + Column("StrMax", INTEGER, nullable=False), + Column("VitMin", INTEGER, nullable=False), + Column("VitMax", INTEGER, nullable=False), + Column("IntMin", INTEGER, nullable=False), + Column("IntMax", INTEGER, nullable=False), + Column("Property1PropertyId", BIGINT, ForeignKey("sao_static_property.PropertyId", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("Property1Value1", INTEGER, nullable=False), + Column("Property1Value2", INTEGER, nullable=False), + Column("Property2PropertyId", BIGINT, ForeignKey("sao_static_property.PropertyId", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("Property2Value1", INTEGER, nullable=False), + Column("Property2Value2", INTEGER, nullable=False), + Column("Property3PropertyId", BIGINT, ForeignKey("sao_static_property.PropertyId", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("Property3Value1", INTEGER, nullable=False), + Column("Property3Value2", INTEGER, nullable=False), + Column("Property4PropertyId", BIGINT, ForeignKey("sao_static_property.PropertyId", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("Property4Value1", INTEGER, nullable=False), + Column("Property4Value2", INTEGER, nullable=False), + Column("FlavorText", VARCHAR(255), nullable=False), + Column("FlavorText_en", VARCHAR(255)), + Column("SalePrice", INTEGER, nullable=False), + Column("CompositionExp", INTEGER, nullable=False), + Column("AwakeningExp", INTEGER, nullable=False), + Column("Slot4UnlockLevel", INTEGER, nullable=False), + Column("Slot5UnlockLevel", INTEGER, nullable=False), + Column("CollectionEmptyFrameDisplayFlag", BOOLEAN, nullable=False), + mysql_charset="utf8mb4" ) item = Table( "sao_static_item_list", metadata, - Column("id", Integer, primary_key=True, nullable=False), - Column("version", Integer), - Column("itemId", Integer), - Column("itemTypeId", Integer), - Column("name", String(255)), - Column("rarity", Integer), - Column("flavorText", String(255)), - Column("enabled", Boolean), - UniqueConstraint( - "version", "itemId", name="sao_static_item_list_uk" - ), - mysql_charset="utf8mb4", + Column("ItemId", INTEGER, nullable=False, primary_key=True), + Column("ItemTypeId", INTEGER, nullable=False), + Column("Name", VARCHAR(255), nullable=False), + Column("Name_en", VARCHAR(255)), + Column("Rarity", INTEGER, nullable=False), + Column("Value", INTEGER, nullable=False), + Column("PropertyId", BIGINT, ForeignKey("sao_static_property.PropertyId", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("PropertyValue1Min", INTEGER, nullable=False), + Column("PropertyValue1Max", INTEGER, nullable=False), + Column("PropertyValue2Min", INTEGER, nullable=False), + Column("PropertyValue2Max", INTEGER, nullable=False), + Column("FlavorText", VARCHAR(255), nullable=False), + Column("FlavorText_en", VARCHAR(255)), + Column("SalePrice", INTEGER, nullable=False), + Column("ItemIcon", VARCHAR(255), nullable=False), + mysql_charset="utf8mb4" ) support = Table( @@ -93,7 +205,7 @@ support = Table( UniqueConstraint( "version", "supportLogId", name="sao_static_support_log_list_uk" ), - mysql_charset="utf8mb4", + mysql_charset="utf8mb4" ) rare_drop = Table( @@ -107,7 +219,7 @@ rare_drop = Table( UniqueConstraint( "version", "questRareDropId", "commonRewardId", name="sao_static_rare_drop_list_uk" ), - mysql_charset="utf8mb4", + mysql_charset="utf8mb4" ) title = Table( @@ -124,91 +236,361 @@ title = Table( UniqueConstraint( "version", "titleId", name="sao_static_title_list_uk" ), - mysql_charset="utf8mb4", + mysql_charset="utf8mb4" +) + +reward = Table( + "sao_static_reward", + metadata, + Column("RewardTableId", BIGINT, primary_key=True, nullable=False), + Column("RewardTableSubId", INTEGER, nullable=False), + Column("UnanalyzedLogGradeId", INTEGER, nullable=False), + Column("CommonRewardType", INTEGER, nullable=False), + Column("CommonRewardId", INTEGER, nullable=False), + Column("CommonRewardNum", INTEGER, nullable=False), + Column("StrengthMin", INTEGER, nullable=False), + Column("StrengthMax", INTEGER, nullable=False), + Column("PropertyTableSubId", INTEGER, nullable=False), + Column("QuestInfoDisplayFlag", BOOLEAN, nullable=False), + Column("Rate", INTEGER, nullable=False), + mysql_charset="utf8mb4" +) + +ex_bonus = Table( + "sao_static_ex_bonus", + metadata, + Column("ExBonusTableId", BIGINT, primary_key=True, nullable=False), + Column("ExBonusTableSubId", INTEGER, nullable=False), + Column("ExBonusConditionId", INTEGER, nullable=False), + Column("ConditionValue1", INTEGER, nullable=False), + Column("ConditionValue2", INTEGER, nullable=False), + Column("CommonRewardType", INTEGER, nullable=False), + Column("CommonRewardId", INTEGER, nullable=False), + Column("CommonRewardNum", INTEGER, nullable=False), + Column("Strength", INTEGER, nullable=False), + Column("Property1PropertyId", BIGINT, ForeignKey("sao_static_property.PropertyId", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("Property1Value1", INTEGER, nullable=False), + Column("Property1Value2", INTEGER, nullable=False), + Column("Property2PropertyId", BIGINT, ForeignKey("sao_static_property.PropertyId", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("Property2Value1", INTEGER, nullable=False), + Column("Property2Value2", INTEGER, nullable=False), + Column("Property3PropertyId", BIGINT, ForeignKey("sao_static_property.PropertyId", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("Property3Value1", INTEGER, nullable=False), + Column("Property3Value2", INTEGER, nullable=False), + Column("Property4PropertyId", BIGINT, ForeignKey("sao_static_property.PropertyId", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("Property4Value1", INTEGER, nullable=False), + Column("Property4Value2", INTEGER, nullable=False), + mysql_charset="utf8mb4" +) + +episode = Table( + "sao_static_episode", + metadata, + Column("EpisodeId", BIGINT, nullable=False, primary_key=True), + Column("EpisodeChapterId", INTEGER, nullable=False), + Column("ReleaseEpisodeId", INTEGER, nullable=False), + Column("Title", VARCHAR(255), nullable=False), + Column("CommentSummary", VARCHAR(255), nullable=False), + Column("ExBonusTableSubId", INTEGER, nullable=False), + Column("QuestSceneId", BIGINT, ForeignKey("sao_static_quest.QuestSceneId", ondelete="cascade", onupdate="cascade")), + mysql_charset="utf8mb4" +) + +tower = Table( + "sao_static_tower", + metadata, + Column("TowerId", BIGINT, nullable=False, primary_key=True), + Column("ReleaseTowerId", INTEGER, nullable=False), + Column("ExBonusTableSubId", INTEGER, nullable=False), + Column("QuestSceneId", BIGINT, ForeignKey("sao_static_quest.QuestSceneId", ondelete="cascade", onupdate="cascade"), nullable=False), + mysql_charset="utf8mb4" +) + +ex_tower = Table( + "sao_static_ex_tower", + metadata, + Column("ExTowerQuestId", BIGINT, nullable=False, primary_key=True), + Column("ExTowerId", INTEGER, nullable=False), + Column("ReleaseExTowerQuestId", INTEGER, nullable=False), + Column("Title", VARCHAR(255), nullable=False), + Column("Title_en", VARCHAR(255)), + Column("ExBonusTableSubId", INTEGER, nullable=False), + Column("QuestSceneId", BIGINT, ForeignKey("sao_static_quest.QuestSceneId", ondelete="cascade", onupdate="cascade"), nullable=False), + mysql_charset="utf8mb4" +) + +side_quest = Table( + "sao_static_side_quest", + metadata, + Column("SideQuestId", BIGINT, nullable=False, unique=True, primary_key=True), + Column("DisplayName", VARCHAR(255), nullable=False), + Column("DisplayName_en", VARCHAR(255)), + Column("EpisodeNum", INTEGER, nullable=False), + Column("ExBonusTableSubId", INTEGER, nullable=False), + Column("QuestSceneId", BIGINT, ForeignKey("sao_static_quest.QuestSceneId", ondelete="cascade", onupdate="cascade"), nullable=False), + mysql_charset="utf8mb4" ) class SaoStaticData(BaseData): - async def put_quest( self, questSceneId: int, version: int, sortNo: int, name: str, enabled: bool ) -> Optional[int]: + async def put_quest(self, data: Dict[str, str]) -> Optional[int]: sql = insert(quest).values( - questSceneId=questSceneId, - version=version, - sortNo=sortNo, - name=name, - enabled=enabled, + QuestSceneId=data["QuestSceneId"], + SortNo=data["SortNo"], + Tutorial=data["Tutorial"], + ColRate=data["ColRate"], + LimitDefault=data["LimitDefault"], + LimitResurrection=data["LimitResurrection"], + RewardTableSubId=data["RewardTableSubId"], + PlayerTraceTableSubId=data["PlayerTraceTableSubId"], + SuccessPlayerExp=data["SuccessPlayerExp"], + FailedPlayerExp=data["FailedPlayerExp"], + PairExpRate=data["PairExpRate"], + TrioExpRate=data["TrioExpRate"], + SingleRewardVp=data["SingleRewardVp"], + PairRewardVp=data["PairRewardVp"], + TrioRewardVp=data["TrioRewardVp"], ) - conflict = sql.on_duplicate_key_update( - name=name, questSceneId=questSceneId, version=version - ) + conflict = sql.on_duplicate_key_update(QuestSceneId=data["QuestSceneId"]) result = await self.execute(conflict) if result is None: return None - return result.lastrowid + return data["QuestSceneId"] - async def put_hero( self, version: int, heroLogId: int, name: str, nickname: str, rarity: int, skillTableSubId: int, awakeningExp: int, flavorText: str, enabled: bool ) -> Optional[int]: + async def put_hero(self, data: Dict[str, str]) -> Optional[int]: sql = insert(hero).values( - version=version, - heroLogId=heroLogId, - name=name, - nickname=nickname, - rarity=rarity, - skillTableSubId=skillTableSubId, - awakeningExp=awakeningExp, - flavorText=flavorText, - enabled=enabled + HeroLogId=data["HeroLogId"], + CharaId=data["CharaId"], + Name=data["Name"], + Nickname=data["Nickname"], + Rarity=data["Rarity"], + WeaponTypeId=data["WeaponTypeId"], + HeroLogRoleId=data["HeroLogRoleId"], + CostumeTypeId=data["CostumeTypeId"], + UnitId=data["UnitId"], + DefaultEquipmentId1=data["DefaultEquipmentId1"], + DefaultEquipmentId2=data["DefaultEquipmentId2"], + SkillTableSubId=data["SkillTableSubId"], + HpMin=data["HpMin"], + HpMax=data["HpMax"], + StrMin=data["StrMin"], + StrMax=data["StrMax"], + VitMin=data["VitMin"], + VitMax=data["VitMax"], + IntMin=data["IntMin"], + IntMax=data["IntMax"], + Property1PropertyId=data["Property1PropertyId"], + Property1Value1=data["Property1Value1"], + Property1Value2=data["Property1Value2"], + Property2PropertyId=data["Property2PropertyId"], + Property2Value1=data["Property2Value1"], + Property2Value2=data["Property2Value2"], + Property3PropertyId=data["Property3PropertyId"], + Property3Value1=data["Property3Value1"], + Property3Value2=data["Property3Value2"], + Property4PropertyId=data["Property4PropertyId"], + Property4Value1=data["Property4Value1"], + Property4Value2=data["Property4Value2"], + FlavorText=data["FlavorText"], + SalePrice=data["SalePrice"], + CompositionExp=data["CompositionExp"], + AwakeningExp=data["AwakeningExp"], + Slot4UnlockLevel=data["Slot4UnlockLevel"], + Slot5UnlockLevel=data["Slot5UnlockLevel"], + CollectionEmptyFrameDisplayFlag=data["CollectionEmptyFrameDisplayFlag"], ) - conflict = sql.on_duplicate_key_update( - name=name, heroLogId=heroLogId - ) + conflict = sql.on_duplicate_key_update(HeroLogId=data["HeroLogId"]) result = await self.execute(conflict) if result is None: return None - return result.lastrowid + return data["HeroLogId"] - async def put_equipment( self, version: int, equipmentId: int, name: str, equipmentType: int, weaponTypeId:int, rarity: int, flavorText: str, enabled: bool ) -> Optional[int]: + async def put_hero_translation(self, hero_id: int, name_en: str, nickname_en: str, flavor_text_en: str) -> None: + result = await self.execute(hero.update(hero.c.HeroLogId == hero_id).values( + Name=name_en, + Nickname=nickname_en, + FlavorText=flavor_text_en, + )) + + result = await self.execute(result) + if result is None: + self.logger.error(f"Failed to add translated text for hero {hero_id}") + + async def put_equipment(self, data: Dict[str, str]) -> Optional[int]: sql = insert(equipment).values( - version=version, - equipmentId=equipmentId, - name=name, - equipmentType=equipmentType, - weaponTypeId=weaponTypeId, - rarity=rarity, - flavorText=flavorText, - enabled=enabled + EquipmentId=data["EquipmentId"], + EquipmentType=data["EquipmentType"], + WeaponTypeId=data["WeaponTypeId"], + Name=data["Name"], + Rarity=data["Rarity"], + Power=data["Power"], + StrengthIncrement=data["StrengthIncrement"], + SkillCondition=data["SkillCondition"], + Property1PropertyId=data["Property1PropertyId"], + Property1Value1=data["Property1Value1"], + Property1Value2=data["Property1Value2"], + Property2PropertyId=data["Property2PropertyId"], + Property2Value1=data["Property2Value1"], + Property2Value2=data["Property2Value2"], + Property3PropertyId=data["Property3PropertyId"], + Property3Value1=data["Property3Value1"], + Property3Value2=data["Property3Value2"], + Property4PropertyId=data["Property4PropertyId"], + Property4Value1=data["Property4Value1"], + Property4Value2=data["Property4Value2"], + SalePrice=data["SalePrice"], + CompositionExp=data["CompositionExp"], + AwakeningExp=data["AwakeningExp"], + FlavorText=data["FlavorText"], ) - conflict = sql.on_duplicate_key_update( - name=name, equipmentId=equipmentId - ) + conflict = sql.on_duplicate_key_update(EquipmentId=data["EquipmentId"]) result = await self.execute(conflict) if result is None: return None - return result.lastrowid + return data["EquipmentId"] - async def put_item( self, version: int, itemId: int, name: str, itemTypeId: int, rarity: int, flavorText: str, enabled: bool ) -> Optional[int]: + async def put_equipment_translation(self, equip_id: int, name_en: str, flavor_text_en: str) -> None: + result = await self.execute(equipment.update(equipment.c.EquipmentId == equip_id).values( + Name_en=name_en, + FlavorText_en=flavor_text_en, + )) + + result = await self.execute(result) + if result is None: + self.logger.error(f"Failed to add translated text for equipment {equip_id}") + + async def put_item(self, data: Dict[str, str]) -> Optional[int]: sql = insert(item).values( - version=version, - itemId=itemId, - name=name, - itemTypeId=itemTypeId, - rarity=rarity, - flavorText=flavorText, - enabled=enabled + ItemId=data["ItemId"], + ItemTypeId=data["ItemTypeId"], + Name=data["Name"], + Rarity=data["Rarity"], + Value=data["Value"], + PropertyId=data["PropertyId"], + PropertyValue1Min=data["PropertyValue1Min"], + PropertyValue1Max=data["PropertyValue1Max"], + PropertyValue2Min=data["PropertyValue2Min"], + PropertyValue2Max=data["PropertyValue2Max"], + FlavorText=data["FlavorText"], + SalePrice=data["SalePrice"], + ItemIcon=data["ItemIcon"], + ) - conflict = sql.on_duplicate_key_update( - name=name, itemId=itemId - ) + conflict = sql.on_duplicate_key_update(ItemId=data["ItemId"]) result = await self.execute(conflict) if result is None: return None - return result.lastrowid + return data["ItemId"] + + async def put_item_translation(self, item_id: int, name_en: str, flavor_text_en: str) -> None: + result = await self.execute(item.update(item.c.ItemId == item_id).values( + Name_en=name_en, + FlavorText_en=flavor_text_en, + )) + + result = await self.execute(result) + if result is None: + self.logger.error(f"Failed to add translated text for item {item_id}") + + async def put_property(self, data: Dict[str, str]) -> None: + sql = insert(prop).values( + PropertyId=data["PropertyId"], + PropertyTargetType=data["PropertyTargetType"], + PropertyName=data["PropertyName"], + PropertyNameFormat=data["PropertyNameFormat"], + PropertyTypeId=data["PropertyTypeId"], + Value1Min=data["Value1Min"], + Value1Max=data["Value1Max"], + Value2Min=data["Value2Min"], + Value2Max=data["Value2Max"], + ) + + conflict = sql.on_duplicate_key_update(PropertyId=data["PropertyId"]) + + result = await self.execute(conflict) + if result is None: + return None + return data["PropertyId"] + async def put_property_translation(self, property_id: int, name_en: str, name_fmt: str) -> None: + result = await self.execute(prop.update(prop.c.PropertyId == property_id).values( + Name_en=name_en, + PropertyNameFormat_en=name_fmt, + )) + + result = await self.execute(result) + if result is None: + self.logger.error(f"Failed to add translated text for property {property_id}") + + async def put_skill(self, data: Dict[str, str]) -> None: + sql = insert(skill).values( + SkillId=data["SkillId"], + WeaponTypeId=data["WeaponTypeId"], + Name=data["Name"], + Attack=data["Attack"], + Passive=data["Passive"], + Pet=data["Pet"], + Level=data["Level"], + SkillCondition=data["SkillCondition"], + CoolTime=data["CoolTime"], + SkillIcon=data["SkillIcon"], + FriendSkillIcon=data["FriendSkillIcon"], + InfoText=data["InfoText"], + ) + + conflict = sql.on_duplicate_key_update(SkillId=data["SkillId"]) + + result = await self.execute(conflict) + if result is None: + return None + return data["SkillId"] + + async def put_skill_translation(self, skill_id: int, name_en: str, info_txt: str) -> None: + result = await self.execute(skill.update(skill.c.SkillId == skill_id).values( + Name_en=name_en, + InfoText_en=info_txt, + )) + + result = await self.execute(result) + if result is None: + self.logger.error(f"Failed to add translated text for skill {skill_id}") + + async def put_skill_table(self, skill_id: int, sub_id: int, level: int, awakening: int, table_id: int) -> None: + sql = insert(skill_table).values( + SkillTableId=table_id, + SkillId=skill_id, + SkillTableSubId=sub_id, + LevelObtained=level, + AwakeningId=awakening, + ) + conflict = sql.on_duplicate_key_update(SkillTableId=table_id) + result = await self.execute(conflict) + if result is None: + self.logger.error(f"Failed to add skill table {skill_id}") + + async def put_player_trace(self, data: Dict[str, str]) -> None: + sql = insert(player_trace).values( + PlayerTraceTableId=data["PlayerTraceTableId"], + PlayerTraceTableSubId=data["PlayerTraceTableSubId"], + CommonRewardType=data["CommonRewardType"], + CommonRewardId=data["CommonRewardId"], + CommonRewardNum=data["CommonRewardNum"], + Rate=data["Rate"], + ) + + conflict = sql.on_duplicate_key_update(PlayerTraceTableId=data["PlayerTraceTableId"]) + result = await self.execute(conflict) + if result is None: + return None + return data["PlayerTraceTableId"] + async def put_support_log( self, version: int, supportLogId: int, charaId: int, name: str, rarity: int, salePrice: int, skillName: str, enabled: bool ) -> Optional[int]: sql = insert(support).values( version=version, @@ -221,14 +603,12 @@ class SaoStaticData(BaseData): enabled=enabled ) - conflict = sql.on_duplicate_key_update( - name=name, supportLogId=supportLogId - ) + conflict = sql.on_duplicate_key_update(version=version) result = await self.execute(conflict) - if result is None: - return None - return result.lastrowid + if result: + return result.inserted_primary_key['id'] + self.logger.error(f"Failed to insert support log {supportLogId}!") async def put_rare_drop( self, version: int, questRareDropId: int, commonRewardId: int, enabled: bool ) -> Optional[int]: sql = insert(rare_drop).values( @@ -238,14 +618,12 @@ class SaoStaticData(BaseData): enabled=enabled, ) - conflict = sql.on_duplicate_key_update( - questRareDropId=questRareDropId, commonRewardId=commonRewardId, version=version - ) + conflict = sql.on_duplicate_key_update(version=version) result = await self.execute(conflict) - if result is None: - return None - return result.lastrowid + if result: + return result.inserted_primary_key['id'] + self.logger.error(f"Failed to insert rare drop {questRareDropId}!") async def put_title( self, version: int, titleId: int, displayName: str, requirement: int, rank: int, imageFilePath: str, enabled: bool ) -> Optional[int]: sql = insert(title).values( @@ -258,71 +636,141 @@ class SaoStaticData(BaseData): enabled=enabled ) - conflict = sql.on_duplicate_key_update( - displayName=displayName, titleId=titleId + conflict = sql.on_duplicate_key_update(version=version) + + result = await self.execute(conflict) + if result: + return result.inserted_primary_key['id'] + self.logger.error(f"Failed to insert title {titleId}") + + async def put_reward_table(self, data: Dict[str, Union[str, bool]]) -> None: + sql = insert(reward).values( + **data ) + conflict = sql.on_duplicate_key_update(**data) + result = await self.execute(conflict) if result is None: return None - return result.lastrowid + return data['RewardTableId'] + + async def put_ex_bonus(self, data: Dict[str, int]) -> None: + sql = insert(ex_bonus).values( + **data + ) - async def get_quests_id(self, sortNo: int) -> Optional[Dict]: - sql = quest.select(quest.c.sortNo == sortNo) - - result = await self.execute(sql) + conflict = sql.on_duplicate_key_update(**data) + + result = await self.execute(conflict) + if result is None: + return None + return data['ExBonusTableId'] + + async def put_episode(self, data: Dict[str, int]) -> None: + sql = insert(episode).values( + EpisodeId=data['EpisodeId'], + EpisodeChapterId=data['EpisodeChapterId'], + ReleaseEpisodeId=data['ReleaseEpisodeId'], + Title=data['Title'], + CommentSummary=data['CommentSummary'], + ExBonusTableSubId=data['ExBonusTableSubId'], + QuestSceneId=data['QuestSceneId'] if data['QuestSceneId'] > 0 else None, + ) + + conflict = sql.on_duplicate_key_update(EpisodeId=data['EpisodeId']) + + result = await self.execute(conflict) + if result is None: + return None + return data['EpisodeId'] + + async def put_tower(self, data: Dict[str, int]) -> None: + sql = insert(tower).values( + TowerId=data['TrialTowerId'], + ReleaseTowerId=data['ReleaseTrialTowerId'], + ExBonusTableSubId=data['ExBonusTableSubId'], + QuestSceneId=data['QuestSceneId'], + ) + + conflict = sql.on_duplicate_key_update(TowerId=data['TrialTowerId']) + + result = await self.execute(conflict) + if result is None: + return None + return data['TrialTowerId'] + + async def put_ex_tower(self, data: Dict[str, int]) -> None: + sql = insert(ex_tower).values( + ExTowerQuestId=data['ExTowerQuestId'], + ExTowerId=data['ExTowerId'], + ReleaseExTowerQuestId=data['ReleaseExTowerQuestId'], + Title=data['Title'], + ExBonusTableSubId=data['ExBonusTableSubId'], + QuestSceneId=data['QuestSceneId'], + ) + + conflict = sql.on_duplicate_key_update(ExTowerQuestId=data['ExTowerQuestId']) + + result = await self.execute(conflict) + if result is None: + return None + return data['ExTowerQuestId'] + + async def put_side_quest(self, data: Dict[str, int]) -> None: + sql = insert(side_quest).values( + SideQuestId=data['SideQuestId'], + DisplayName=data['DisplayName'], + EpisodeNum=data['EpisodeNum'], + ExBonusTableSubId=data['ExBonusTableSubId'], + QuestSceneId=data['QuestSceneId'], + ) + + conflict = sql.on_duplicate_key_update(SideQuestId=data['SideQuestId']) + + result = await self.execute(conflict) + if result is None: + return None + return data['SideQuestId'] + + async def get_quest_by_id(self, quest_scene_id: int) -> Optional[Row]: + result = await self.execute(quest.select(quest.c.QuestSceneId == quest_scene_id)) if result is None: return None return result.fetchone() - async def get_quests_ids(self, version: int, enabled: bool) -> Optional[List[Dict]]: - sql = quest.select(quest.c.version == version and quest.c.enabled == enabled).order_by( - quest.c.questSceneId.asc() - ) - - result = await self.execute(sql) + async def get_quests(self) -> Optional[List[Row]]: + result = await self.execute(quest.select()) if result is None: return None - return [list[2] for list in result.fetchall()] + return result.fetchall() - async def get_hero_id(self, heroLogId: int) -> Optional[Dict]: - sql = hero.select(hero.c.heroLogId == heroLogId) - - result = await self.execute(sql) + async def get_hero_by_id(self, heroLogId: int) -> Optional[Row]: + result = await self.execute(hero.select(hero.c.HeroLogId == heroLogId)) if result is None: return None return result.fetchone() - async def get_hero_ids(self, version: int, enabled: bool) -> Optional[List[Dict]]: - sql = hero.select(hero.c.version == version and hero.c.enabled == enabled).order_by( - hero.c.heroLogId.asc() - ) - - result = await self.execute(sql) + async def get_heros(self) -> Optional[List[Row]]: + result = await self.execute(hero.select()) if result is None: return None - return [list[2] for list in result.fetchall()] + return result.fetchall() - async def get_equipment_id(self, equipmentId: int) -> Optional[Dict]: - sql = equipment.select(equipment.c.equipmentId == equipmentId) - - result = await self.execute(sql) + async def get_equipment_by_id(self, equipmentId: int) -> Optional[Dict]: + result = await self.execute(equipment.select(equipment.c.EquipmentId == equipmentId)) if result is None: return None return result.fetchone() - async def get_equipment_ids(self, version: int, enabled: bool) -> Optional[List[Dict]]: - sql = equipment.select(equipment.c.version == version and equipment.c.enabled == enabled).order_by( - equipment.c.equipmentId.asc() - ) - - result = await self.execute(sql) + async def get_equipment(self) -> Optional[List[Dict]]: + result = await self.execute(equipment.select()) if result is None: return None - return [list[2] for list in result.fetchall()] + return result.fetchall() - async def get_item_id(self, itemId: int) -> Optional[Dict]: - sql = item.select(item.c.itemId == itemId) + async def get_item_id(self, item_id: int) -> Optional[Dict]: + sql = item.select(item.c.ItemId == item_id) result = await self.execute(sql) if result is None: @@ -347,6 +795,12 @@ class SaoStaticData(BaseData): return None return [list[2] for list in result.fetchall()] + async def get_support_logs(self) -> Optional[List[Dict]]: + result = await self.execute(support.select()) + if result is None: + return None + return result.fetchall() + async def get_support_log_ids(self, version: int, enabled: bool) -> Optional[List[Dict]]: sql = support.select(support.c.version == version and support.c.enabled == enabled).order_by( support.c.supportLogId.asc() @@ -365,4 +819,156 @@ class SaoStaticData(BaseData): result = await self.execute(sql) if result is None: return None - return [list[2] for list in result.fetchall()] \ No newline at end of file + return [list[2] for list in result.fetchall()] + + async def get_reward_by_table(self, table_id: int) -> Optional[Row]: + result = await self.execute(reward.select(reward.c.RewardTableId == table_id)) + if result is None: + return None + return result.fetchone() + + async def get_rewards_by_subtable(self, subtable_id: int) -> Optional[List[Row]]: + result = await self.execute(reward.select(reward.c.RewardTableSubId == subtable_id)) + if result is None: + return None + return result.fetchall() + + async def get_rewards(self) -> Optional[List[Row]]: + result = await self.execute(reward.select()) + if result is None: + return None + return result.fetchall() + + async def get_ex_bonus_by_table(self, table_id: int) -> Optional[Row]: + result = await self.execute(ex_bonus.select(ex_bonus.c.ExBonusTableId == table_id)) + if result is None: + return None + return result.fetchone() + + async def get_ex_bonuses_by_subtable(self, subtable_id: int) -> Optional[List[Row]]: + result = await self.execute(ex_bonus.select(ex_bonus.c.ExBonusTableSubId == subtable_id)) + if result is None: + return None + return result.fetchall() + + async def get_ex_bonuses(self) -> Optional[List[Row]]: + result = await self.execute(ex_bonus.select()) + if result is None: + return None + return result.fetchall() + + async def get_episode_by_id(self, episode_id: int) -> Optional[Row]: + result = await self.execute(episode.select(episode.c.EpisodeId == episode_id)) + if result is None: + return None + return result.fetchone() + + async def get_episode_by_quest_id(self, quest_id: int) -> Optional[Row]: + result = await self.execute(episode.select(episode.c.QuestSceneId == quest_id)) + if result is None: + return None + return result.fetchone() + + async def get_episodes(self) -> Optional[List[Row]]: + result = await self.execute(episode.select()) + if result is None: + return None + return result.fetchall() + + async def get_tower_by_id(self, tower_id: int) -> Optional[Row]: + result = await self.execute(tower.select(tower.c.TowerId == tower_id)) + if result is None: + return None + return result.fetchone() + + async def get_tower_by_quest_id(self, quest_id: int) -> Optional[Row]: + result = await self.execute(tower.select(tower.c.QuestSceneId == quest_id)) + if result is None: + return None + return result.fetchone() + + async def get_towers(self) -> Optional[List[Row]]: + result = await self.execute(tower.select()) + if result is None: + return None + return result.fetchall() + + async def get_ex_tower_by_id(self, ex_tower_id: int) -> Optional[Row]: + result = await self.execute(ex_tower.select(ex_tower.c.ExTowerQuestId == ex_tower_id)) + if result is None: + return None + return result.fetchone() + + async def get_ex_tower_by_quest_id(self, quest_id: int) -> Optional[Row]: + result = await self.execute(ex_tower.select(ex_tower.c.QuestSceneId == quest_id)) + if result is None: + return None + return result.fetchone() + + async def get_ex_towers(self) -> Optional[List[Row]]: + result = await self.execute(ex_tower.select()) + if result is None: + return None + return result.fetchall() + + async def get_ex_towers_by_tower(self, ex_tower_id: int) -> Optional[List[Row]]: + result = await self.execute(ex_tower.select(ex_tower.c.ExTowerId == ex_tower_id)) + if result is None: + return None + return result.fetchall() + + async def get_side_quest_by_id(self, side_quest_id: int) -> Optional[Row]: + result = await self.execute(side_quest.select(side_quest.c.SideQuestId == side_quest_id)) + if result is None: + return None + return result.fetchone() + + async def get_side_quest_by_quest_id(self, quest_id: int) -> Optional[Row]: + result = await self.execute(side_quest.select(side_quest.c.QuestSceneId == quest_id)) + if result is None: + return None + return result.fetchone() + + async def get_side_quests(self) -> Optional[List[Row]]: + result = await self.execute(side_quest.select()) + if result is None: + return None + return result.fetchall() + + async def get_side_quests_by_episode(self, episode_num: int) -> Optional[List[Row]]: + result = await self.execute(side_quest.select(side_quest.c.EpisodeNum == episode_num)) + if result is None: + return None + return result.fetchall() + + async def get_player_trace_by_id(self, trace_id: int) -> Optional[Row]: + result = await self.execute(player_trace.select(player_trace.c.PlayerTraceTableId == trace_id)) + if result is None: + return None + return result.fetchone() + + async def get_player_trace_by_subid(self, trace_sub_id: int) -> Optional[List[Row]]: + result = await self.execute(player_trace.select(player_trace.c.PlayerTraceTableSubId == trace_sub_id)) + if result is None: + return None + return result.fetchall() + + async def get_player_traces(self) -> Optional[List[Row]]: + result = await self.execute(player_trace.select()) + if result is None: + return None + return result.fetchall() + + async def get_skill_table_by_subid(self, table_sub_id: int) -> Optional[List[Row]]: + result = await self.execute(skill_table.select(skill_table.c.SkillTableSubId == table_sub_id)) + + if result is None: + return None + return result.fetchall() + + async def get_skill_by_id(self, skill_id: int) -> Optional[Row]: + result = await self.execute(skill.select(skill.c.SkillId == skill_id)) + + if result is None: + return None + return result.fetchone() diff --git a/titles/sao/templates/sao_index.jinja b/titles/sao/templates/sao_index.jinja new file mode 100644 index 0000000..35d42ce --- /dev/null +++ b/titles/sao/templates/sao_index.jinja @@ -0,0 +1,182 @@ +{% extends "core/templates/index.jinja" %} +{% block content %} +