Merge branch 'develop' into fork_develop

This commit is contained in:
Dniel97
2023-04-30 20:39:35 +02:00
101 changed files with 2688 additions and 691 deletions
+1
View File
@@ -158,5 +158,6 @@ cert/*
!cert/server.pem !cert/server.pem
config/* config/*
deliver/* deliver/*
*.gz
dbdump-*.json dbdump-*.json
+49
View File
@@ -0,0 +1,49 @@
# Changelog
Documenting updates to ARTEMiS, to be updated every time the master branch is pushed to.
## 2023042300
### Wacca
+ Time free now works properly
+ Fix reverse gate mission causing a fatal error
+ Other misc. fixes
+ Latest DB: 5
### Pokken
+ Added preliminary support
+ Nothing saves currently, but the game will boot and function properly.
### Initial D Zero
+ Added preliminary support
+ Nothing saves currently, but the game will boot and function for the most part.
### Mai2
+ Added support for Festival
+ Lasted DB Version: 4
### Ongeki
+ Misc fixes
+ Lasted DB Version: 4
### Diva
+ Misc fixes
+ Lasted DB Version: 4
### Chuni
+ Fix network encryption
+ Add `handle_remove_token_api_request` for event mode
### Allnet
+ Added download order support
+ It is up to the sysop to provide the INI file, and host the files.
+ ONLY for use with cabs. It's not checked currently, which it's why it's default disabled
+ YMMV, use at your own risk
+ When running develop mode, games that are not recognised will still be able to authenticate.
### Database
+ Add autoupgrade command
+ Invoke to automatically upgrade all schemas to their latest versions
+ `version` arg no longer required, leave it blank to update the game schema to latest if it isn't already
### Misc
+ Update example nginx config file
+45 -6
View File
@@ -10,6 +10,7 @@ from Crypto.PublicKey import RSA
from Crypto.Hash import SHA from Crypto.Hash import SHA
from Crypto.Signature import PKCS1_v1_5 from Crypto.Signature import PKCS1_v1_5
from time import strptime from time import strptime
from os import path
from core.config import CoreConfig from core.config import CoreConfig
from core.utils import Utils from core.utils import Utils
@@ -55,7 +56,7 @@ class AllnetServlet:
self.logger.error("No games detected!") self.logger.error("No games detected!")
for _, mod in plugins.items(): for _, mod in plugins.items():
if hasattr(mod.index, "get_allnet_info"): if hasattr(mod, "index") and hasattr(mod.index, "get_allnet_info"):
for code in mod.game_codes: for code in mod.game_codes:
enabled, uri, host = mod.index.get_allnet_info( enabled, uri, host = mod.index.get_allnet_info(
code, self.config, self.config_folder code, self.config, self.config_folder
@@ -106,7 +107,9 @@ class AllnetServlet:
return self.dict_to_http_form_string([vars(resp)]) return self.dict_to_http_form_string([vars(resp)])
else: else:
self.logger.info(f"Allowed unknown game {req.game_id} v{req.ver} to authenticate from {request_ip} due to 'is_develop' being enabled. S/N: {req.serial}") self.logger.info(
f"Allowed unknown game {req.game_id} v{req.ver} to authenticate from {request_ip} due to 'is_develop' being enabled. S/N: {req.serial}"
)
resp.uri = f"http://{self.config.title.hostname}:{self.config.title.port}/{req.game_id}/{req.ver.replace('.', '')}/" resp.uri = f"http://{self.config.title.hostname}:{self.config.title.port}/{req.game_id}/{req.ver.replace('.', '')}/"
resp.host = f"{self.config.title.hostname}:{self.config.title.port}" resp.host = f"{self.config.title.hostname}:{self.config.title.port}"
return self.dict_to_http_form_string([vars(resp)]) return self.dict_to_http_form_string([vars(resp)])
@@ -188,15 +191,51 @@ class AllnetServlet:
self.logger.error(e) self.logger.error(e)
return b"" return b""
self.logger.info(f"DownloadOrder from {request_ip} -> {req.game_id} v{req.ver} serial {req.serial}") self.logger.info(
f"DownloadOrder from {request_ip} -> {req.game_id} v{req.ver} serial {req.serial}"
)
resp = AllnetDownloadOrderResponse() resp = AllnetDownloadOrderResponse()
if not self.config.allnet.allow_online_updates: if (
not self.config.allnet.allow_online_updates
or not self.config.allnet.update_cfg_folder
):
return self.dict_to_http_form_string([vars(resp)]) return self.dict_to_http_form_string([vars(resp)])
else: # TODO: Actual dlorder response else: # TODO: Keychip check
if path.exists(
f"{self.config.allnet.update_cfg_folder}/{req.game_id}-{req.ver}-app.ini"
):
resp.uri = f"http://{self.config.title.hostname}:{self.config.title.port}/dl/ini/{req.game_id}-{req.ver.replace('.', '')}-app.ini"
if path.exists(
f"{self.config.allnet.update_cfg_folder}/{req.game_id}-{req.ver}-opt.ini"
):
resp.uri += f"|http://{self.config.title.hostname}:{self.config.title.port}/dl/ini/{req.game_id}-{req.ver.replace('.', '')}-opt.ini"
self.logger.debug(f"Sending download uri {resp.uri}")
return self.dict_to_http_form_string([vars(resp)]) return self.dict_to_http_form_string([vars(resp)])
def handle_dlorder_ini(self, request: Request, match: Dict) -> bytes:
if "file" not in match:
return b""
req_file = match["file"].replace("%0A", "")
if path.exists(f"{self.config.allnet.update_cfg_folder}/{req_file}"):
return open(
f"{self.config.allnet.update_cfg_folder}/{req_file}", "rb"
).read()
self.logger.info(f"DL INI File {req_file} not found")
return b""
def handle_dlorder_report(self, request: Request, match: Dict) -> bytes:
self.logger.info(
f"DLI Report from {Utils.get_ip_addr(request)}: {request.content.getvalue()}"
)
return b""
def handle_billing_request(self, request: Request, _: Dict): def handle_billing_request(self, request: Request, _: Dict):
req_dict = self.billing_req_to_dict(request.content.getvalue()) req_dict = self.billing_req_to_dict(request.content.getvalue())
request_ip = Utils.get_ip_addr(request) request_ip = Utils.get_ip_addr(request)
@@ -419,7 +458,7 @@ class AllnetDownloadOrderRequest:
class AllnetDownloadOrderResponse: class AllnetDownloadOrderResponse:
def __init__(self, stat: int = 1, serial: str = "", uri: str = "null") -> None: def __init__(self, stat: int = 1, serial: str = "", uri: str = "") -> None:
self.stat = stat self.stat = stat
self.serial = serial self.serial = serial
self.uri = uri self.uri = uri
+6
View File
@@ -188,6 +188,12 @@ class AllnetConfig:
self.__config, "core", "allnet", "allow_online_updates", default=False self.__config, "core", "allnet", "allow_online_updates", default=False
) )
@property
def update_cfg_folder(self) -> str:
return CoreConfig.get_config_field(
self.__config, "core", "allnet", "update_cfg_folder", default=""
)
class BillingConfig: class BillingConfig:
def __init__(self, parent_config: "CoreConfig") -> None: def __init__(self, parent_config: "CoreConfig") -> None:
+72 -16
View File
@@ -1,5 +1,5 @@
import logging, coloredlogs import logging, coloredlogs
from typing import Optional from typing import Optional, Dict, List
from sqlalchemy.orm import scoped_session, sessionmaker from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy import create_engine from sqlalchemy import create_engine
@@ -32,7 +32,7 @@ class Data:
self.arcade = ArcadeData(self.config, self.session) self.arcade = ArcadeData(self.config, self.session)
self.card = CardData(self.config, self.session) self.card = CardData(self.config, self.session)
self.base = BaseData(self.config, self.session) self.base = BaseData(self.config, self.session)
self.schema_ver_latest = 4 self.current_schema_version = 4
log_fmt_str = "[%(asctime)s] %(levelname)s | Database | %(message)s" log_fmt_str = "[%(asctime)s] %(levelname)s | Database | %(message)s"
log_fmt = logging.Formatter(log_fmt_str) log_fmt = logging.Formatter(log_fmt_str)
@@ -71,7 +71,9 @@ class Data:
games = Utils.get_all_titles() games = Utils.get_all_titles()
for game_dir, game_mod in games.items(): for game_dir, game_mod in games.items():
try: try:
if hasattr(game_mod, "database") and hasattr(game_mod, "current_schema_version"): if hasattr(game_mod, "database") and hasattr(
game_mod, "current_schema_version"
):
game_mod.database(self.config) game_mod.database(self.config)
metadata.create_all(self.__engine.connect()) metadata.create_all(self.__engine.connect())
@@ -84,8 +86,8 @@ class Data:
f"Could not load database schema from {game_dir} - {e}" f"Could not load database schema from {game_dir} - {e}"
) )
self.logger.info(f"Setting base_schema_ver to {self.schema_ver_latest}") self.logger.info(f"Setting base_schema_ver to {self.current_schema_version}")
self.base.set_schema_ver(self.schema_ver_latest) self.base.set_schema_ver(self.current_schema_version)
self.logger.info( self.logger.info(
f"Setting user auto_incrememnt to {self.config.database.user_table_autoincrement_start}" f"Setting user auto_incrememnt to {self.config.database.user_table_autoincrement_start}"
@@ -129,9 +131,32 @@ class Data:
self.create_database() self.create_database()
def migrate_database(self, game: str, version: int, action: str) -> None: def migrate_database(self, game: str, version: Optional[int], action: str) -> None:
old_ver = self.base.get_schema_ver(game) old_ver = self.base.get_schema_ver(game)
sql = "" sql = ""
if version is None:
if not game == "CORE":
titles = Utils.get_all_titles()
for folder, mod in titles.items():
if not mod.game_codes[0] == game:
continue
if hasattr(mod, "current_schema_version"):
version = mod.current_schema_version
else:
self.logger.warn(
f"current_schema_version not found for {folder}"
)
else:
version = self.current_schema_version
if version is None:
self.logger.warn(
f"Could not determine latest version for {game}, please specify --version"
)
if old_ver is None: if old_ver is None:
self.logger.error( self.logger.error(
@@ -263,17 +288,48 @@ class Data:
self.user.delete_user(user["id"]) self.user.delete_user(user["id"])
def autoupgrade(self) -> None: def autoupgrade(self) -> None:
all_games = self.base.get_all_schema_vers() all_game_versions = self.base.get_all_schema_vers()
if all_games is None: if all_game_versions is None:
self.logger.warn("Failed to get schema versions") self.logger.warn("Failed to get schema versions")
return
for x in all_games: all_games = Utils.get_all_titles()
all_games_list: Dict[str, int] = {}
for _, mod in all_games.items():
if hasattr(mod, "current_schema_version"):
all_games_list[mod.game_codes[0]] = mod.current_schema_version
for x in all_game_versions:
failed = False
game = x["game"].upper() game = x["game"].upper()
update_ver = 1 update_ver = int(x["version"])
for y in range(2, 100): latest_ver = all_games_list.get(game, 1)
if os.path.exists(f"core/data/schema/versions/{game}_{y}_upgrade.sql"): if game == "CORE":
update_ver = y latest_ver = self.current_schema_version
else:
break
self.migrate_database(game, update_ver, "upgrade") if update_ver == latest_ver:
self.logger.info(f"{game} is already latest version")
continue
for y in range(update_ver + 1, latest_ver + 1):
if os.path.exists(f"core/data/schema/versions/{game}_{y}_upgrade.sql"):
with open(
f"core/data/schema/versions/{game}_{y}_upgrade.sql",
"r",
encoding="utf-8",
) as f:
sql = f.read()
result = self.base.execute(sql)
if result is None:
self.logger.error(
f"Error execuing sql script for game {game} v{y}!"
)
failed = True
break
else:
self.logger.warning(f"Could not find script {game}_{y}_upgrade.sql")
failed = True
if not failed:
self.base.set_schema_ver(latest_ver, game)
+1 -1
View File
@@ -47,7 +47,7 @@ class BaseData:
res = None res = None
try: try:
self.logger.info(f"SQL Execute: {''.join(str(sql).splitlines())} || {opts}") self.logger.info(f"SQL Execute: {''.join(str(sql).splitlines())}")
res = self.conn.execute(text(sql), opts) res = self.conn.execute(text(sql), opts)
except SQLAlchemyError as e: except SQLAlchemyError as e:
@@ -0,0 +1,31 @@
ALTER TABLE mai2_profile_option
DROP COLUMN tapSe;
ALTER TABLE mai2_score_best
DROP COLUMN extNum1;
ALTER TABLE mai2_profile_extend
DROP COLUMN playStatusSetting;
ALTER TABLE mai2_playlog
DROP COLUMN extNum4;
ALTER TABLE mai2_static_event
DROP COLUMN startDate;
ALTER TABLE mai2_item_map
CHANGE COLUMN mapId map_id INT NOT NULL,
CHANGE COLUMN isLock is_lock BOOLEAN NOT NULL DEFAULT 0,
CHANGE COLUMN isClear is_clear BOOLEAN NOT NULL DEFAULT 0,
CHANGE COLUMN isComplete is_complete BOOLEAN NOT NULL DEFAULT 0;
ALTER TABLE mai2_item_friend_season_ranking
CHANGE COLUMN seasonId season_id INT NOT NULL,
CHANGE COLUMN rewardGet reward_get BOOLEAN NOT NULL,
CHANGE COLUMN userName user_name VARCHAR(8) NOT NULL,
CHANGE COLUMN recordDate record_date VARCHAR(255) NOT NULL;
ALTER TABLE mai2_item_login_bonus
CHANGE COLUMN bonusId bonus_id INT NOT NULL,
CHANGE COLUMN isCurrent is_current BOOLEAN NOT NULL DEFAULT 0,
CHANGE COLUMN isComplete is_complete BOOLEAN NOT NULL DEFAULT 0;
@@ -0,0 +1,31 @@
ALTER TABLE mai2_profile_option
ADD COLUMN tapSe INT NOT NULL DEFAULT 0 AFTER tapDesign;
ALTER TABLE mai2_score_best
ADD COLUMN extNum1 INT NOT NULL DEFAULT 0;
ALTER TABLE mai2_profile_extend
ADD COLUMN playStatusSetting INT NOT NULL DEFAULT 0;
ALTER TABLE mai2_playlog
ADD COLUMN extNum4 INT NOT NULL DEFAULT 0;
ALTER TABLE mai2_static_event
ADD COLUMN startDate TIMESTAMP NOT NULL DEFAULT current_timestamp();
ALTER TABLE mai2_item_map
CHANGE COLUMN map_id mapId INT NOT NULL,
CHANGE COLUMN is_lock isLock BOOLEAN NOT NULL DEFAULT 0,
CHANGE COLUMN is_clear isClear BOOLEAN NOT NULL DEFAULT 0,
CHANGE COLUMN is_complete isComplete BOOLEAN NOT NULL DEFAULT 0;
ALTER TABLE mai2_item_friend_season_ranking
CHANGE COLUMN season_id seasonId INT NOT NULL,
CHANGE COLUMN reward_get rewardGet BOOLEAN NOT NULL,
CHANGE COLUMN user_name userName VARCHAR(8) NOT NULL,
CHANGE COLUMN record_date recordDate TIMESTAMP NOT NULL;
ALTER TABLE mai2_item_login_bonus
CHANGE COLUMN bonus_id bonusId INT NOT NULL,
CHANGE COLUMN is_current isCurrent BOOLEAN NOT NULL DEFAULT 0,
CHANGE COLUMN is_complete isComplete BOOLEAN NOT NULL DEFAULT 0;
@@ -0,0 +1,2 @@
SET FOREIGN_KEY_CHECKS=0;
SET FOREIGN_KEY_CHECKS=1;
@@ -0,0 +1 @@
ALTER TABLE wacca_profile DROP COLUMN playcount_time_free;
@@ -0,0 +1 @@
DELETE FROM wacca_item WHERE type=17 AND item_id=312002;
@@ -0,0 +1 @@
ALTER TABLE wacca_profile ADD playcount_time_free int(11) DEFAULT 0 NULL AFTER playcount_stageup;
+5 -2
View File
@@ -71,8 +71,11 @@ class FrontendServlet(resource.Resource):
game_fe = game_mod.frontend(cfg, self.environment, config_dir) game_fe = game_mod.frontend(cfg, self.environment, config_dir)
self.game_list.append({"url": game_dir, "name": game_fe.nav_name}) self.game_list.append({"url": game_dir, "name": game_fe.nav_name})
fe_game.putChild(game_dir.encode(), game_fe) fe_game.putChild(game_dir.encode(), game_fe)
except:
raise except Exception as e:
self.logger.error(
f"Failed to import frontend from {game_dir} because {e}"
)
self.environment.globals["game_list"] = self.game_list self.environment.globals["game_list"] = self.game_list
self.putChild(b"gate", FE_Gate(cfg, self.environment)) self.putChild(b"gate", FE_Gate(cfg, self.environment))
+3 -9
View File
@@ -46,9 +46,7 @@ class MuchaServlet:
if enabled: if enabled:
self.mucha_registry.append(game_cd) self.mucha_registry.append(game_cd)
self.logger.info( self.logger.info(f"Serving {len(self.mucha_registry)} games")
f"Serving {len(self.mucha_registry)} games"
)
def handle_boardauth(self, request: Request, _: Dict) -> bytes: def handle_boardauth(self, request: Request, _: Dict) -> bytes:
req_dict = self.mucha_preprocess(request.content.getvalue()) req_dict = self.mucha_preprocess(request.content.getvalue())
@@ -62,9 +60,7 @@ class MuchaServlet:
req = MuchaAuthRequest(req_dict) req = MuchaAuthRequest(req_dict)
self.logger.debug(f"Mucha request {vars(req)}") self.logger.debug(f"Mucha request {vars(req)}")
self.logger.info( self.logger.info(f"Boardauth request from {client_ip} for {req.gameVer}")
f"Boardauth request from {client_ip} for {req.gameVer}"
)
if req.gameCd not in self.mucha_registry: if req.gameCd not in self.mucha_registry:
self.logger.warn(f"Unknown gameCd {req.gameCd}") self.logger.warn(f"Unknown gameCd {req.gameCd}")
@@ -92,9 +88,7 @@ class MuchaServlet:
req = MuchaUpdateRequest(req_dict) req = MuchaUpdateRequest(req_dict)
self.logger.debug(f"Mucha request {vars(req)}") self.logger.debug(f"Mucha request {vars(req)}")
self.logger.info( self.logger.info(f"Updatecheck request from {client_ip} for {req.gameVer}")
f"Updatecheck request from {client_ip} for {req.gameVer}"
)
if req.gameCd not in self.mucha_registry: if req.gameCd not in self.mucha_registry:
self.logger.warn(f"Unknown gameCd {req.gameCd}") self.logger.warn(f"Unknown gameCd {req.gameCd}")
+8 -1
View File
@@ -16,6 +16,9 @@ class Utils:
if not dir.startswith("__"): if not dir.startswith("__"):
try: try:
mod = importlib.import_module(f"titles.{dir}") mod = importlib.import_module(f"titles.{dir}")
if hasattr(mod, "game_codes") and hasattr(
mod, "index"
): # Minimum required to function
ret[dir] = mod ret[dir] = mod
except ImportError as e: except ImportError as e:
@@ -25,4 +28,8 @@ class Utils:
@classmethod @classmethod
def get_ip_addr(cls, req: Request) -> str: def get_ip_addr(cls, req: Request) -> str:
return req.getAllHeaders()[b"x-forwarded-for"].decode() if b"x-forwarded-for" in req.getAllHeaders() else req.getClientAddress().host return (
req.getAllHeaders()[b"x-forwarded-for"].decode()
if b"x-forwarded-for" in req.getAllHeaders()
else req.getClientAddress().host
)
+16 -7
View File
@@ -1,5 +1,6 @@
import yaml import yaml
import argparse import argparse
import logging
from core.config import CoreConfig from core.config import CoreConfig
from core.data import Data from core.data import Data
from os import path, mkdir, access, W_OK from os import path, mkdir, access, W_OK
@@ -32,7 +33,9 @@ if __name__ == "__main__":
cfg = CoreConfig() cfg = CoreConfig()
if path.exists(f"{args.config}/core.yaml"): if path.exists(f"{args.config}/core.yaml"):
cfg.update(yaml.safe_load(open(f"{args.config}/core.yaml"))) cfg_dict = yaml.safe_load(open(f"{args.config}/core.yaml"))
cfg_dict.get("database", {})["loglevel"] = "info"
cfg.update(cfg_dict)
if not path.exists(cfg.server.log_dir): if not path.exists(cfg.server.log_dir):
mkdir(cfg.server.log_dir) mkdir(cfg.server.log_dir)
@@ -45,7 +48,6 @@ if __name__ == "__main__":
data = Data(cfg) data = Data(cfg)
if args.action == "create": if args.action == "create":
data.create_database() data.create_database()
@@ -54,15 +56,22 @@ if __name__ == "__main__":
elif args.action == "upgrade" or args.action == "rollback": elif args.action == "upgrade" or args.action == "rollback":
if args.version is None: if args.version is None:
data.logger.error("Must set game and version to migrate to") data.logger.warn("No version set, upgrading to latest")
exit(0)
if args.game is None: if args.game is None:
data.logger.info("No game set, upgrading core schema") data.logger.warn("No game set, upgrading core schema")
data.migrate_database("CORE", int(args.version), args.action) data.migrate_database(
"CORE",
int(args.version) if args.version is not None else None,
args.action,
)
else: else:
data.migrate_database(args.game, int(args.version), args.action) data.migrate_database(
args.game,
int(args.version) if args.version is not None else None,
args.action,
)
elif args.action == "autoupgrade": elif args.action == "autoupgrade":
data.autoupgrade() data.autoupgrade()
+7 -12
View File
@@ -64,8 +64,7 @@ which version is the latest, f.e. `SDBT_3_upgrade.sql`. In order to upgrade to v
perform all previous updates as well: perform all previous updates as well:
```shell ```shell
python dbutils.py --game SDBT --version 2 upgrade python dbutils.py --game SDBT upgrade
python dbutils.py --game SDBT --version 3 upgrade
``` ```
## crossbeats REV. ## crossbeats REV.
@@ -114,6 +113,7 @@ Config file is located in `config/cxb.yaml`.
| 3 | maimai DX Splash PLUS | | 3 | maimai DX Splash PLUS |
| 4 | maimai DX Universe | | 4 | maimai DX Universe |
| 5 | maimai DX Universe PLUS | | 5 | maimai DX Universe PLUS |
| 6 | maimai DX Festival |
### Importer ### Importer
@@ -126,14 +126,14 @@ python read.py --series SDEZ --version <version ID> --binfolder /path/to/game/fo
The importer for maimai DX will import Events, Music and Tickets. The importer for maimai DX will import Events, Music and Tickets.
**NOTE: It is required to use the importer because the game will **NOTE: It is required to use the importer because the game will
crash without it!** crash without Events!**
### Database upgrade ### Database upgrade
Always make sure your database (tables) are up-to-date, to do so go to the `core/data/schema/versions` folder and see which version is the latest, f.e. `SDEZ_2_upgrade.sql`. In order to upgrade to version 2 in this case you need to perform all previous updates as well: Always make sure your database (tables) are up-to-date, to do so go to the `core/data/schema/versions` folder and see which version is the latest, f.e. `SDEZ_2_upgrade.sql`. In order to upgrade to version 2 in this case you need to perform all previous updates as well:
```shell ```shell
python dbutils.py --game SDEZ --version 2 upgrade python dbutils.py --game SDEZ upgrade
``` ```
## Hatsune Miku Project Diva ## Hatsune Miku Project Diva
@@ -174,9 +174,7 @@ which version is the latest, f.e. `SBZV_4_upgrade.sql`. In order to upgrade to v
perform all previous updates as well: perform all previous updates as well:
```shell ```shell
python dbutils.py --game SBZV --version 2 upgrade python dbutils.py --game SBZV upgrade
python dbutils.py --game SBZV --version 3 upgrade
python dbutils.py --game SBZV --version 4 upgrade
``` ```
## O.N.G.E.K.I. ## O.N.G.E.K.I.
@@ -224,9 +222,7 @@ which version is the latest, f.e. `SDDT_4_upgrade.sql`. In order to upgrade to v
perform all previous updates as well: perform all previous updates as well:
```shell ```shell
python dbutils.py --game SDDT --version 2 upgrade python dbutils.py --game SDDT upgrade
python dbutils.py --game SDDT --version 3 upgrade
python dbutils.py --game SDDT --version 4 upgrade
``` ```
## Card Maker ## Card Maker
@@ -346,6 +342,5 @@ Config file is located in `config/wacca.yaml`.
Always make sure your database (tables) are up-to-date, to do so go to the `core/data/schema/versions` folder and see which version is the latest, f.e. `SDFE_3_upgrade.sql`. In order to upgrade to version 3 in this case you need to perform all previous updates as well: Always make sure your database (tables) are up-to-date, to do so go to the `core/data/schema/versions` folder and see which version is the latest, f.e. `SDFE_3_upgrade.sql`. In order to upgrade to version 3 in this case you need to perform all previous updates as well:
```shell ```shell
python dbutils.py --game SDFE --version 2 upgrade python dbutils.py --game SDFE upgrade
python dbutils.py --game SDFE --version 3 upgrade
``` ```
+1
View File
@@ -32,6 +32,7 @@ allnet:
loglevel: "info" loglevel: "info"
port: 80 port: 80
allow_online_updates: False allow_online_updates: False
update_cfg_folder: ""
billing: billing:
port: 8443 port: 8443
+11
View File
@@ -0,0 +1,11 @@
server:
enable: True
loglevel: "info"
hostname: ""
news: ""
aes_key: ""
ports:
userdb: 10000
match: 10010
echo: 10020
+3
View File
@@ -29,5 +29,8 @@ gates:
- 17 - 17
- 18 - 18
- 19 - 19
- 20
- 21 - 21
- 22 - 22
- 23
- 24
+16
View File
@@ -26,6 +26,22 @@ class HttpDispatcher(resource.Resource):
self.title = TitleServlet(cfg, config_dir) self.title = TitleServlet(cfg, config_dir)
self.mucha = MuchaServlet(cfg, config_dir) self.mucha = MuchaServlet(cfg, config_dir)
self.map_get.connect(
"allnet_downloadorder_ini",
"/dl/ini/{file}",
controller="allnet",
action="handle_dlorder_ini",
conditions=dict(method=["GET"]),
)
self.map_post.connect(
"allnet_downloadorder_report",
"/dl/report",
controller="allnet",
action="handle_dlorder_report",
conditions=dict(method=["POST"]),
)
self.map_post.connect( self.map_post.connect(
"allnet_ping", "allnet_ping",
"/naomitest.html", "/naomitest.html",
+1 -2
View File
@@ -135,8 +135,7 @@ if __name__ == "__main__":
for dir, mod in titles.items(): for dir, mod in titles.items():
if args.series in mod.game_codes: if args.series in mod.game_codes:
handler = mod.reader(config, args.version, handler = mod.reader(config, args.version, bin_arg, opt_arg, args.extra)
bin_arg, opt_arg, args.extra)
handler.read() handler.read()
logger.info("Done") logger.info("Done")
+5 -3
View File
@@ -2,15 +2,15 @@
A network service emulator for games running SEGA'S ALL.NET service, and similar. A network service emulator for games running SEGA'S ALL.NET service, and similar.
# Supported games # Supported games
Games listed below have been tested and confirmed working. Only game versions older then the current one in active use in arcades (n-0) or current game versions older then a year (y-1) are supported. Games listed below have been tested and confirmed working. Only game versions older then the version currently active in arcades, or games versions that have not recieved a major update in over one year, are supported.
+ Chunithm + Chunithm
+ All versions up to New!! Plus + All versions up to New!! Plus
+ Crossbeats Rev + Crossbeats Rev
+ All versions + omnimix + All versions + omnimix
+ Maimai + maimai DX
+ All versions up to Universe Plus + All versions up to Festival
+ Hatsune Miku Arcade + Hatsune Miku Arcade
+ All versions + All versions
@@ -26,6 +26,8 @@ Games listed below have been tested and confirmed working. Only game versions ol
+ Lily R + Lily R
+ Reverse + Reverse
+ Pokken
+ Final Online
## Requirements ## Requirements
- python 3 (tested working with 3.9 and 3.10, other versions YMMV) - python 3 (tested working with 3.9 and 3.10, other versions YMMV)
+27 -9
View File
@@ -89,14 +89,26 @@ class ChuniServlet:
self.hash_table[version] = {} self.hash_table[version] = {}
method_list = [method for method in dir(self.versions[version]) if not method.startswith('__')] method_list = [
method
for method in dir(self.versions[version])
if not method.startswith("__")
]
for method in method_list: for method in method_list:
method_fixed = inflection.camelize(method)[6:-7] method_fixed = inflection.camelize(method)[6:-7]
hash = PBKDF2(method_fixed, bytes.fromhex(keys[2]), 128, count=44, hmac_hash_module=SHA1) hash = PBKDF2(
method_fixed,
bytes.fromhex(keys[2]),
128,
count=44,
hmac_hash_module=SHA1,
)
self.hash_table[version][hash.hex()] = method_fixed self.hash_table[version][hash.hex()] = method_fixed
self.logger.debug(f"Hashed v{version} method {method_fixed} with {bytes.fromhex(keys[2])} to get {hash.hex()}") self.logger.debug(
f"Hashed v{version} method {method_fixed} with {bytes.fromhex(keys[2])} to get {hash.hex()}"
)
@classmethod @classmethod
def get_allnet_info( def get_allnet_info(
@@ -167,11 +179,15 @@ class ChuniServlet:
else: else:
if internal_ver not in self.hash_table: if internal_ver not in self.hash_table:
self.logger.error(f"v{version} does not support encryption or no keys entered") self.logger.error(
f"v{version} does not support encryption or no keys entered"
)
return zlib.compress(b'{"stat": "0"}') return zlib.compress(b'{"stat": "0"}')
elif endpoint.lower() not in self.hash_table[internal_ver]: elif endpoint.lower() not in self.hash_table[internal_ver]:
self.logger.error(f"No hash found for v{version} endpoint {endpoint}") self.logger.error(
f"No hash found for v{version} endpoint {endpoint}"
)
return zlib.compress(b'{"stat": "0"}') return zlib.compress(b'{"stat": "0"}')
endpoint = self.hash_table[internal_ver][endpoint.lower()] endpoint = self.hash_table[internal_ver][endpoint.lower()]
@@ -193,7 +209,11 @@ class ChuniServlet:
encrtped = True encrtped = True
if not encrtped and self.game_cfg.crypto.encrypted_only and internal_ver >= ChuniConstants.VER_CHUNITHM_CRYSTAL_PLUS: if (
not encrtped
and self.game_cfg.crypto.encrypted_only
and internal_ver >= ChuniConstants.VER_CHUNITHM_CRYSTAL_PLUS
):
self.logger.error( self.logger.error(
f"Unencrypted v{version} {endpoint} request, but config is set to encrypted only: {req_raw}" f"Unencrypted v{version} {endpoint} request, but config is set to encrypted only: {req_raw}"
) )
@@ -210,9 +230,7 @@ class ChuniServlet:
req_data = json.loads(unzip) req_data = json.loads(unzip)
self.logger.info( self.logger.info(f"v{version} {endpoint} request from {client_ip}")
f"v{version} {endpoint} request from {client_ip}"
)
self.logger.debug(req_data) self.logger.debug(req_data)
func_to_find = "handle_" + inflection.underscore(endpoint) + "_request" func_to_find = "handle_" + inflection.underscore(endpoint) + "_request"
+6 -2
View File
@@ -13,8 +13,12 @@ class ChuniNewPlus(ChuniNew):
def handle_get_game_setting_api_request(self, data: Dict) -> Dict: def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data) ret = super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["romVersion"] = self.game_cfg.version.version(self.version)["rom"] ret["gameSetting"]["romVersion"] = self.game_cfg.version.version(self.version)[
ret["gameSetting"]["dataVersion"] = self.game_cfg.version.version(self.version)["data"] "rom"
]
ret["gameSetting"]["dataVersion"] = self.game_cfg.version.version(self.version)[
"data"
]
ret["gameSetting"][ ret["gameSetting"][
"matchingUri" "matchingUri"
] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/205/ChuniServlet/" ] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/205/ChuniServlet/"
+3 -1
View File
@@ -200,7 +200,9 @@ class ChuniStaticData(BaseData):
return result.lastrowid return result.lastrowid
def get_login_bonus( def get_login_bonus(
self, version: int, preset_id: int, self,
version: int,
preset_id: int,
) -> Optional[List[Row]]: ) -> Optional[List[Row]]:
sql = login_bonus.select( sql = login_bonus.select(
and_( and_(
+4 -1
View File
@@ -12,6 +12,7 @@ from twisted.web.http import Request
from logging.handlers import TimedRotatingFileHandler from logging.handlers import TimedRotatingFileHandler
from core.config import CoreConfig from core.config import CoreConfig
from core.utils import Utils
from titles.cm.config import CardMakerConfig from titles.cm.config import CardMakerConfig
from titles.cm.const import CardMakerConstants from titles.cm.const import CardMakerConstants
from titles.cm.base import CardMakerBase from titles.cm.base import CardMakerBase
@@ -82,6 +83,7 @@ class CardMakerServlet:
url_split = url_path.split("/") url_split = url_path.split("/")
internal_ver = 0 internal_ver = 0
endpoint = url_split[len(url_split) - 1] endpoint = url_split[len(url_split) - 1]
client_ip = Utils.get_ip_addr(request)
print(f"version: {version}") print(f"version: {version}")
@@ -107,7 +109,8 @@ class CardMakerServlet:
req_data = json.loads(unzip) req_data = json.loads(unzip)
self.logger.info(f"v{version} {endpoint} request - {req_data}") self.logger.info(f"v{version} {endpoint} request from {client_ip}")
self.logger.debug(req_data)
func_to_find = "handle_" + inflection.underscore(endpoint) + "_request" func_to_find = "handle_" + inflection.underscore(endpoint) + "_request"
+3 -2
View File
@@ -80,7 +80,7 @@ class CardMakerReader(BaseReader):
for dir in data_dirs: for dir in data_dirs:
self.read_chuni_card(f"{dir}/CHU/card") self.read_chuni_card(f"{dir}/CHU/card")
self.read_chuni_gacha(f"{dir}/CHU/gacha") self.read_chuni_gacha(f"{dir}/CHU/gacha")
self.read_mai2_card(f"{dir}/MAI/card")
self.read_ongeki_gacha(f"{dir}/MU3/gacha") self.read_ongeki_gacha(f"{dir}/MU3/gacha")
def read_chuni_card(self, base_dir: str) -> None: def read_chuni_card(self, base_dir: str) -> None:
@@ -90,7 +90,7 @@ class CardMakerReader(BaseReader):
"v2_00": ChuniConstants.VER_CHUNITHM_NEW, "v2_00": ChuniConstants.VER_CHUNITHM_NEW,
"v2_05": ChuniConstants.VER_CHUNITHM_NEW_PLUS, "v2_05": ChuniConstants.VER_CHUNITHM_NEW_PLUS,
# Chunithm SUN, ignore for now # Chunithm SUN, ignore for now
"v2_10": ChuniConstants.VER_CHUNITHM_NEW_PLUS + 1 "v2_10": ChuniConstants.VER_CHUNITHM_NEW_PLUS + 1,
} }
for root, dirs, files in os.walk(base_dir): for root, dirs, files in os.walk(base_dir):
@@ -206,6 +206,7 @@ class CardMakerReader(BaseReader):
"1.15": Mai2Constants.VER_MAIMAI_DX_SPLASH_PLUS, "1.15": Mai2Constants.VER_MAIMAI_DX_SPLASH_PLUS,
"1.20": Mai2Constants.VER_MAIMAI_DX_UNIVERSE, "1.20": Mai2Constants.VER_MAIMAI_DX_UNIVERSE,
"1.25": Mai2Constants.VER_MAIMAI_DX_UNIVERSE_PLUS, "1.25": Mai2Constants.VER_MAIMAI_DX_UNIVERSE_PLUS,
"1.30": Mai2Constants.VER_MAIMAI_DX_FESTIVAL,
} }
for root, dirs, files in os.walk(base_dir): for root, dirs, files in os.walk(base_dir):
+1 -3
View File
@@ -101,9 +101,7 @@ class CxbServlet(resource.Resource):
f"Ready on ports {self.game_cfg.server.port} & {self.game_cfg.server.port_secure}" f"Ready on ports {self.game_cfg.server.port} & {self.game_cfg.server.port_secure}"
) )
else: else:
self.logger.info( self.logger.info(f"Ready on port {self.game_cfg.server.port}")
f"Ready on port {self.game_cfg.server.port}"
)
def render_POST(self, request: Request): def render_POST(self, request: Request):
version = 0 version = 0
+1 -1
View File
@@ -7,4 +7,4 @@ index = DivaServlet
database = DivaData database = DivaData
reader = DivaReader reader = DivaReader
game_codes = [DivaConstants.GAME_CODE] game_codes = [DivaConstants.GAME_CODE]
current_schema_version = 1 current_schema_version = 4
+8
View File
@@ -0,0 +1,8 @@
from titles.idz.index import IDZServlet
from titles.idz.const import IDZConstants
from titles.idz.database import IDZData
index = IDZServlet
database = IDZData
game_codes = [IDZConstants.GAME_CODE]
current_schema_version = 1
+73
View File
@@ -0,0 +1,73 @@
from typing import List, Dict
from core.config import CoreConfig
class IDZServerConfig:
def __init__(self, parent_config: "IDZConfig") -> None:
self.__config = parent_config
@property
def enable(self) -> bool:
return CoreConfig.get_config_field(
self.__config, "idz", "server", "enable", default=True
)
@property
def loglevel(self) -> int:
return CoreConfig.str_to_loglevel(
CoreConfig.get_config_field(
self.__config, "idz", "server", "loglevel", default="info"
)
)
@property
def hostname(self) -> str:
return CoreConfig.get_config_field(
self.__config, "idz", "server", "hostname", default=""
)
@property
def news(self) -> str:
return CoreConfig.get_config_field(
self.__config, "idz", "server", "news", default=""
)
@property
def aes_key(self) -> str:
return CoreConfig.get_config_field(
self.__config, "idz", "server", "aes_key", default=""
)
class IDZPortsConfig:
def __init__(self, parent_config: "IDZConfig") -> None:
self.__config = parent_config
@property
def userdb(self) -> int:
return CoreConfig.get_config_field(
self.__config, "idz", "ports", "userdb", default=10000
)
@property
def match(self) -> int:
return CoreConfig.get_config_field(
self.__config, "idz", "ports", "match", default=10010
)
@property
def echo(self) -> int:
return CoreConfig.get_config_field(
self.__config, "idz", "ports", "echo", default=10020
)
class IDZConfig(dict):
def __init__(self) -> None:
self.server = IDZServerConfig(self)
self.ports = IDZPortsConfig(self)
@property
def rsa_keys(self) -> List[Dict]:
return CoreConfig.get_config_field(self, "idz", "rsa_keys", default=[])
+53
View File
@@ -0,0 +1,53 @@
from enum import Enum
class IDZConstants:
GAME_CODE = "SDDF"
CONFIG_NAME = "idz.yaml"
VER_IDZ_110 = 0
VER_IDZ_130 = 1
VER_IDZ_210 = 2
VER_IDZ_230 = 3
NUM_VERS = 4
VERSION_NAMES = (
"Initial D Arcade Stage Zero v1.10",
"Initial D Arcade Stage Zero v1.30",
"Initial D Arcade Stage Zero v2.10",
"Initial D Arcade Stage Zero v2.30",
)
class PROFILE_STATUS(Enum):
LOCKED = 0
UNLOCKED = 1
OLD = 2
HASH_LUT = [
# No clue
0x9C82E674,
0x5A4738D9,
0x8B8D7AE0,
0x29EC9D81,
# These three are from AES TE0
0x1209091B,
0x1D83839E,
0x582C2C74,
0x341A1A2E,
0x361B1B2D,
0xDC6E6EB2,
0xB45A5AEE,
0x5BA0A0FB,
0xA45252F6,
0x763B3B4D,
0xB7D6D661,
0x7DB3B3CE,
]
HASH_NUM = 0
HASH_MUL = [5, 7, 11, 12][HASH_NUM]
HASH_XOR = [0xB3, 0x8C, 0x14, 0x50][HASH_NUM]
@classmethod
def game_ver_to_string(cls, ver: int):
return cls.VERSION_NAMES[ver]
+7
View File
@@ -0,0 +1,7 @@
from core.data import Data
from core.config import CoreConfig
class IDZData(Data):
def __init__(self, cfg: CoreConfig) -> None:
super().__init__(cfg)
+19
View File
@@ -0,0 +1,19 @@
from twisted.internet.protocol import DatagramProtocol
import logging
from core.config import CoreConfig
from .config import IDZConfig
class IDZEcho(DatagramProtocol):
def __init__(self, cfg: CoreConfig, game_cfg: IDZConfig) -> None:
super().__init__()
self.core_config = cfg
self.game_config = game_cfg
self.logger = logging.getLogger("idz")
def datagramReceived(self, data, addr):
self.logger.debug(
f"Echo from from {addr[0]}:{addr[1]} -> {self.transport.getHost().port} - {data.hex()}"
)
self.transport.write(data, addr)
+47
View File
@@ -0,0 +1,47 @@
from .base import IDZHandlerBase
from .load_server_info import IDZHandlerLoadServerInfo
from .load_ghost import IDZHandlerLoadGhost
from .load_config import IDZHandlerLoadConfigA, IDZHandlerLoadConfigB
from .load_top_ten import IDZHandlerLoadTopTen
from .update_story_clear_num import IDZHandlerUpdateStoryClearNum
from .save_expedition import IDZHandlerSaveExpedition
from .load_2on2 import IDZHandlerLoad2on2A, IDZHandlerLoad2on2B
from .load_team_ranking import IDZHandlerLoadTeamRankingA, IDZHandlerLoadTeamRankingB
from .discover_profile import IDZHandlerDiscoverProfile
from .lock_profile import IDZHandlerLockProfile
from .check_team_names import IDZHandlerCheckTeamName
from .unknown import IDZHandlerUnknown
from .create_profile import IDZHandlerCreateProfile
from .create_auto_team import IDZHandlerCreateAutoTeam
from .load_profile import IDZHandlerLoadProfile
from .save_profile import IDZHandlerSaveProfile
from .update_provisional_store_rank import IDZHandlerUpdateProvisionalStoreRank
from .load_reward_table import IDZHandlerLoadRewardTable
from .save_topic import IDZHandlerSaveTopic
from .save_time_attack import IDZHandlerSaveTimeAttack
from .unlock_profile import IDZHandlerUnlockProfile
from .update_team_points import IDZHandleUpdateTeamPoints
from .update_ui_report import IDZHandleUpdateUIReport
+26
View File
@@ -0,0 +1,26 @@
import logging
import struct
from core.data import Data
from core.config import CoreConfig
from ..config import IDZConfig
from ..const import IDZConstants
class IDZHandlerBase:
name = "generic"
cmd_codes = [0x0000] * IDZConstants.NUM_VERS
rsp_codes = [0x0001] * IDZConstants.NUM_VERS
def __init__(self, core_cfg: CoreConfig, game_cfg: IDZConfig, version: int) -> None:
self.core_config = core_cfg
self.game_cfg = game_cfg
self.data = Data(core_cfg)
self.logger = logging.getLogger("idz")
self.game = IDZConstants.GAME_CODE
self.version = version
self.size = 0x30
def handle(self, data: bytes) -> bytearray:
ret = bytearray([0] * self.size)
struct.pack_into("<H", ret, 0x0, self.rsp_codes[self.version])
return ret
+20
View File
@@ -0,0 +1,20 @@
import struct
from .base import IDZHandlerBase
from core.config import CoreConfig
from ..config import IDZConfig
class IDZHandlerCheckTeamName(IDZHandlerBase):
cmd_codes = [0x00A2, 0x00A2, 0x0097, 0x0097]
rsp_codes = [0x00A3, 0x00A3, 0x0098, 0x0098]
name = "check_team_name"
def __init__(self, core_cfg: CoreConfig, game_cfg: IDZConfig, version: int) -> None:
super().__init__(core_cfg, game_cfg, version)
self.size = 0x0010
def handle(self, data: bytes) -> bytearray:
ret = super().handle(data)
struct.pack_into("<I", ret, 0x4, 0x1)
return data
+63
View File
@@ -0,0 +1,63 @@
from operator import indexOf
import struct
import json
from random import choice, randrange
from .base import IDZHandlerBase
from core.config import CoreConfig
from ..config import IDZConfig
AUTO_TEAM_NAMES = ["スピードスターズ", "レッドサンズ", "ナイトキッズ"]
FULL_WIDTH_NUMS = [
"\uff10",
"\uff11",
"\uff12",
"\uff13",
"\uff14",
"\uff15",
"\uff16",
"\uff17",
"\uff18",
"\uff19",
]
class IDZHandlerCreateAutoTeam(IDZHandlerBase):
cmd_codes = [0x007B, 0x007B, 0x0077, 0x0077]
rsp_codes = [0x007C, 0x007C, 0x0078, 0x0078]
name = "create_auto_team"
def __init__(self, core_cfg: CoreConfig, game_cfg: IDZConfig, version: int) -> None:
super().__init__(core_cfg, game_cfg, version)
self.size = 0x0CA0
def handle(self, data: bytes) -> bytearray:
ret = super().handle(data)
aime_id = struct.unpack_from("<I", data, 0x04)[0]
name = choice(AUTO_TEAM_NAMES)
bg = indexOf(AUTO_TEAM_NAMES, name)
number = (
choice(FULL_WIDTH_NUMS) + choice(FULL_WIDTH_NUMS) + choice(FULL_WIDTH_NUMS)
)
tdata = {
"id": aime_id,
"bg": bg,
"fx": 0,
}
tdata = {
"id": aime_id,
"name": (name + number),
"bg": bg,
"fx": 0,
}
tname = tdata["name"].encode("shift-jis")
struct.pack_into("<I", ret, 0x0C, tdata["id"])
struct.pack_into(f"{len(tname)}s", ret, 0x24, tname)
struct.pack_into("<I", ret, 0x80, tdata["id"])
struct.pack_into(f"<I", ret, 0xD8, tdata["bg"])
struct.pack_into(f"<I", ret, 0xDC, tdata["fx"])
return ret
+73
View File
@@ -0,0 +1,73 @@
import json
import struct
from datetime import datetime
from .base import IDZHandlerBase
from core.config import CoreConfig
from ..config import IDZConfig
class IDZHandlerCreateProfile(IDZHandlerBase):
cmd_codes = [0x0066, 0x0066, 0x0064, 0x0064]
rsp_codes = [0x0067, 0x0065, 0x0065, 0x0065]
name = "create_profile"
def __init__(self, core_cfg: CoreConfig, game_cfg: IDZConfig, version: int) -> None:
super().__init__(core_cfg, game_cfg, version)
self.size = 0x0020
def handle(self, data: bytes) -> bytearray:
ret = super().handle(data)
aime_id = struct.unpack_from("<L", data, 0x04)[0]
name = data[0x1E:0x0034].decode("shift-jis").replace("\x00", "")
car = data[0x40:0xA0].hex()
chara = data[0xA8:0xBC].hex()
self.logger.info(f"Create profile for {name} (aime id {aime_id})")
auto_team = None
if not auto_team:
team = {"bg": 0, "id": 0, "shop": ""}
else:
tdata = json.loads(auto_team["data"])
team = {"bg": tdata["bg"], "id": tdata["fx"], "shop": ""}
profile_data = {
"profile": {
"xp": 0,
"lv": 1,
"fame": 0,
"dpoint": 0,
"milage": 0,
"playstamps": 0,
"last_login": int(datetime.now().timestamp()),
"car_str": car, # These should probably be chaged to dicts
"chara_str": chara, # But this works for now...
},
"options": {
"music": 0,
"pack": 13640,
"aura": 0,
"paper_cup": 0,
"gauges": 5,
"driving_style": 0,
},
"missions": {"team": [], "solo": []},
"story": {"x": 0, "y": 0, "rows": {}},
"unlocks": {
"auras": 1,
"cup": 0,
"gauges": 32,
"music": 0,
"last_mileage_reward": 0,
},
"team": team,
}
if self.version > 2:
struct.pack_into("<L", ret, 0x04, aime_id)
else:
struct.pack_into("<L", ret, 0x08, aime_id)
return ret
View File
+24
View File
@@ -0,0 +1,24 @@
import struct
from typing import Tuple, List, Dict
from .base import IDZHandlerBase
from core.config import CoreConfig
from ..config import IDZConfig
class IDZHandlerDiscoverProfile(IDZHandlerBase):
cmd_codes = [0x006B, 0x0067]
rsp_codes = [0x006C, 0x0068, 0x0068, 0x0068]
name = "discover_profile"
def __init__(self, core_cfg: CoreConfig, game_cfg: IDZConfig, version: int) -> None:
super().__init__(core_cfg, game_cfg, version)
self.size = 0x0010
def handle(self, data: bytes) -> bytearray:
ret = super().handle(data)
user_id = struct.unpack_from("<I", data, 0x04)[0]
profile = None
struct.pack_into("<H", ret, 0x04, int(profile is not None))
return ret
+38
View File
@@ -0,0 +1,38 @@
import struct
from .base import IDZHandlerBase
from core.config import CoreConfig
from ..config import IDZConfig
from ..const import IDZConstants
class IDZHandlerLoad2on2A(IDZHandlerBase):
cmd_codes = [0x00B0, 0x00B0, 0x00A3, 0x00A3]
rsp_codes = [0x00B1, 0x00B1, 0x00A4, 0x00A4]
name = "load_2on2A"
def __init__(self, core_cfg: CoreConfig, game_cfg: IDZConfig, version: int) -> None:
super().__init__(core_cfg, game_cfg, version)
self.size = 0x04C0
if version >= IDZConstants.VER_IDZ_210:
self.size = 0x1290
def handle(self, data: bytes) -> bytearray:
return super().handle(data)
class IDZHandlerLoad2on2B(IDZHandlerBase):
cmd_codes = [0x0132] * 4
rsp_codes = [0x0133] * 4
name = "load_2on2B"
def __init__(self, core_cfg: CoreConfig, game_cfg: IDZConfig, version: int) -> None:
super().__init__(core_cfg, game_cfg, version)
self.size = 0x04C0
if version >= IDZConstants.VER_IDZ_210:
self.size = 0x0540
def handle(self, data: bytes) -> bytearray:
return super().handle(data)
+43
View File
@@ -0,0 +1,43 @@
import struct
from .base import IDZHandlerBase
from core.config import CoreConfig
from ..config import IDZConfig
from ..const import IDZConstants
class IDZHandlerLoadConfigA(IDZHandlerBase):
cmd_codes = [0x0004] * IDZConstants.NUM_VERS
rsp_codes = [0x0005] * IDZConstants.NUM_VERS
name = "load_config_a"
def __init__(self, core_cfg: CoreConfig, game_cfg: IDZConfig, version: int) -> None:
super().__init__(core_cfg, game_cfg, version)
self.size = 0x01A0
if self.version > 1:
self.size = 0x05E0
def handle(self, data: bytes) -> bytearray:
ret = super().handle(data)
struct.pack_into("<H", ret, 0x02, 1)
struct.pack_into("<I", ret, 0x16, 230)
return ret
class IDZHandlerLoadConfigB(IDZHandlerBase):
cmd_codes = [0x00AB, 0x00AB, 0x00A0, 0x00A0]
rsp_codes = [0x00AC, 0x00AC, 0x00A1, 0x00A1]
name = "load_config_b"
def __init__(self, core_cfg: CoreConfig, game_cfg: IDZConfig, version: int) -> None:
super().__init__(core_cfg, game_cfg, version)
self.size = 0x0230
if self.version > 1:
self.size = 0x0240
def handle(self, data: bytes) -> bytearray:
ret = super().handle(data)
struct.pack_into("<H", ret, 0x02, 1)
return ret
+40
View File
@@ -0,0 +1,40 @@
import struct
from .base import IDZHandlerBase
from core.config import CoreConfig
from ..config import IDZConfig
class IDZHandlerLoadGhost(IDZHandlerBase):
cmd_codes = [0x00A0, 0x00A0, 0x0095, 0x0095]
rsp_codes = [0x00A1, 0x00A1, 0x0096, 0x0096]
name = "load_ghost"
def __init__(self, core_cfg: CoreConfig, game_cfg: IDZConfig, version: int) -> None:
super().__init__(core_cfg, game_cfg, version)
self.size = 0x0070
def handle(self, data: bytes) -> bytearray:
ret = super().handle(data)
struct.pack_into("<I", ret, 0x02, 0x5)
struct.pack_into("<L", ret, 0x04, 0x0)
struct.pack_into("<l", ret, 0x08, -1)
struct.pack_into("<L", ret, 0x0C, 0x1D4C0)
struct.pack_into("<L", ret, 0x10, 0x1D4C0)
struct.pack_into("<L", ret, 0x14, 0x1D4C0)
struct.pack_into("<L", ret, 0x38, 0x0)
struct.pack_into("<l", ret, 0x3C, -1)
struct.pack_into("<L", ret, 0x40, 0x1D4C0)
struct.pack_into("<L", ret, 0x44, 0x1D4C0)
struct.pack_into("<L", ret, 0x48, 0x1D4C0)
struct.pack_into("<L", ret, 0x4C, 0x1D4C0)
struct.pack_into("<i", ret, 0x50, -1)
struct.pack_into("<H", ret, 0x52, 0)
struct.pack_into("<H", ret, 0x53, 0)
struct.pack_into("<H", ret, 0x54, 0)
struct.pack_into("<H", ret, 0x55, 0)
struct.pack_into("<H", ret, 0x58, 0)
return ret
+29
View File
@@ -0,0 +1,29 @@
import struct
from .base import IDZHandlerBase
from core.config import CoreConfig
from ..config import IDZConfig
from ..const import IDZConstants
class IDZHandlerLoadProfile(IDZHandlerBase):
cmd_codes = [0x0067, 0x012F, 0x012F, 0x0142]
rsp_codes = [0x0065, 0x012E, 0x012E, 0x0141]
name = "load_profile"
def __init__(self, core_cfg: CoreConfig, game_cfg: IDZConfig, version: int) -> None:
super().__init__(core_cfg, game_cfg, version)
if self.version == IDZConstants.VER_IDZ_110:
self.size = 0x0D30
elif self.version == IDZConstants.VER_IDZ_130:
self.size = 0x0EA0
elif self.version == IDZConstants.VER_IDZ_210:
self.size = 0x1360
elif self.version == IDZConstants.VER_IDZ_230:
self.size = 0x1640
def handle(self, data: bytes) -> bytearray:
ret = super().handle(data)
aime_id = struct.unpack_from("<L", data, 0x04)[0]
return ret
+18
View File
@@ -0,0 +1,18 @@
import struct
from .base import IDZHandlerBase
from core.config import CoreConfig
from ..config import IDZConfig
class IDZHandlerLoadRewardTable(IDZHandlerBase):
cmd_codes = [0x0086, 0x0086, 0x007F, 0x007F]
rsp_codes = [0x0087, 0x0087, 0x0080, 0x0080]
name = "load_reward_table"
def __init__(self, core_cfg: CoreConfig, game_cfg: IDZConfig, version: int) -> None:
super().__init__(core_cfg, game_cfg, version)
self.size = 0x01C0
def handle(self, data: bytes) -> bytearray:
return super().handle(data)
+96
View File
@@ -0,0 +1,96 @@
import struct
from .base import IDZHandlerBase
from core.config import CoreConfig
from ..config import IDZConfig
from ..const import IDZConstants
class IDZHandlerLoadServerInfo(IDZHandlerBase):
cmd_codes = [0x0006] * IDZConstants.NUM_VERS
rsp_codes = [0x0007] * IDZConstants.NUM_VERS
name = "load_server_info1"
def __init__(self, core_cfg: CoreConfig, game_cfg: IDZConfig, version: int) -> None:
super().__init__(core_cfg, game_cfg, version)
self.size = 0x04B0
def handle(self, data: bytes) -> bytearray:
ret = super().handle(data)
offset = 0
if self.version >= IDZConstants.VER_IDZ_210:
offset = 2
news_str = f"http://{self.core_config.title.hostname}:{self.core_config.title.port}/SDDF/230/news/news80**.txt"
err_str = f"http://{self.core_config.title.hostname}:{self.core_config.title.port}/SDDF/230/error"
len_hostname = len(self.core_config.title.hostname)
len_news = len(news_str)
len_error = len(err_str)
struct.pack_into("<I", ret, 0x2 + offset, 1) # Status
struct.pack_into(
f"{len_hostname}s",
ret,
0x4 + offset,
self.core_config.title.hostname.encode(),
)
struct.pack_into("<I", ret, 0x84 + offset, self.game_cfg.ports.userdb)
struct.pack_into("<I", ret, 0x86 + offset, self.game_cfg.ports.userdb + 1)
struct.pack_into(
f"{len_hostname}s",
ret,
0x88 + offset,
self.core_config.title.hostname.encode(),
)
struct.pack_into("<I", ret, 0x108 + offset, self.game_cfg.ports.match - 1)
struct.pack_into("<I", ret, 0x10A + offset, self.game_cfg.ports.match - 3)
struct.pack_into("<I", ret, 0x10C + offset, self.game_cfg.ports.match - 2)
struct.pack_into("<I", ret, 0x10E + offset, self.game_cfg.ports.match + 2)
struct.pack_into("<I", ret, 0x110 + offset, self.game_cfg.ports.match + 3)
struct.pack_into("<I", ret, 0x112 + offset, self.game_cfg.ports.match + 1)
struct.pack_into(
f"{len_hostname}s",
ret,
0x114 + offset,
self.core_config.title.hostname.encode(),
)
struct.pack_into("<I", ret, 0x194 + offset, self.game_cfg.ports.echo + 2)
struct.pack_into(
f"{len_hostname}s",
ret,
0x0199 + offset,
self.core_config.title.hostname.encode(),
)
struct.pack_into("<I", ret, 0x0219 + offset, self.game_cfg.ports.echo + 3)
struct.pack_into(
f"{len_hostname}s",
ret,
0x021C + offset,
self.core_config.title.hostname.encode(),
)
struct.pack_into(
f"{len_hostname}s",
ret,
0x029C + offset,
self.core_config.title.hostname.encode(),
)
struct.pack_into(
f"{len_hostname}s",
ret,
0x031C + offset,
self.core_config.title.hostname.encode(),
)
struct.pack_into("<I", ret, 0x39C + offset, self.game_cfg.ports.echo)
struct.pack_into("<I", ret, 0x39E + offset, self.game_cfg.ports.echo + 1)
struct.pack_into(f"{len_news}s", ret, 0x03A0 + offset, news_str.encode())
struct.pack_into(f"{len_error}s", ret, 0x0424 + offset, err_str.encode())
return ret
+31
View File
@@ -0,0 +1,31 @@
import struct
from .base import IDZHandlerBase
from core.config import CoreConfig
from ..config import IDZConfig
class IDZHandlerLoadTeamRankingA(IDZHandlerBase):
cmd_codes = [0x00B9, 0x00B9, 0x00A7, 0x00A7]
rsp_codes = [0x00B1] * 4
name = "load_team_ranking_a"
def __init__(self, core_cfg: CoreConfig, game_cfg: IDZConfig, version: int) -> None:
super().__init__(core_cfg, game_cfg, version)
self.size = 0x0BA0
def handle(self, data: bytes) -> bytearray:
return super().handle(data)
class IDZHandlerLoadTeamRankingB(IDZHandlerBase):
cmd_codes = [0x00BB, 0x00BB, 0x00A9, 0x00A9]
rsp_codes = [0x00A8] * 4
name = "load_team_ranking_b"
def __init__(self, core_cfg: CoreConfig, game_cfg: IDZConfig, version: int) -> None:
super().__init__(core_cfg, game_cfg, version)
self.size = 0x0BA0
def handle(self, data: bytes) -> bytearray:
return super().handle(data)
+35
View File
@@ -0,0 +1,35 @@
import struct
from typing import Tuple, List, Dict
from .base import IDZHandlerBase
from core.config import CoreConfig
from ..config import IDZConfig
class IDZHandlerLoadTopTen(IDZHandlerBase):
cmd_codes = [0x012C] * 4
rsp_codes = [0x00CE] * 4
name = "load_top_ten"
def __init__(self, core_cfg: CoreConfig, game_cfg: IDZConfig, version: int) -> None:
super().__init__(core_cfg, game_cfg, version)
self.size = 0x1720
def handle(self, data: bytes) -> bytearray:
ret = super().handle(data)
tracks_dates: List[Tuple[int, int]] = []
for i in range(32):
tracks_dates.append(
(
struct.unpack_from("<H", data, 0x04 + (2 * i))[0],
"little",
struct.unpack_from("<I", data, 0x44 + (4 * i))[0],
"little",
)
)
# TODO: Best scores
for i in range(3):
offset = 0x16C0 + 0x1C * i
struct.pack_into("<B", ret, offset + 0x02, 0xFF)
return ret

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