Merge branch 'develop' into fork_develop
This commit is contained in:
@@ -158,5 +158,6 @@ cert/*
|
|||||||
!cert/server.pem
|
!cert/server.pem
|
||||||
config/*
|
config/*
|
||||||
deliver/*
|
deliver/*
|
||||||
|
*.gz
|
||||||
|
|
||||||
dbdump-*.json
|
dbdump-*.json
|
||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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()
|
||||||
|
|||||||
@@ -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
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
server:
|
||||||
|
enable: True
|
||||||
|
loglevel: "info"
|
||||||
|
hostname: ""
|
||||||
|
news: ""
|
||||||
|
aes_key: ""
|
||||||
|
|
||||||
|
ports:
|
||||||
|
userdb: 10000
|
||||||
|
match: 10010
|
||||||
|
echo: 10020
|
||||||
@@ -29,5 +29,8 @@ gates:
|
|||||||
- 17
|
- 17
|
||||||
- 18
|
- 18
|
||||||
- 19
|
- 19
|
||||||
|
- 20
|
||||||
- 21
|
- 21
|
||||||
- 22
|
- 22
|
||||||
|
- 23
|
||||||
|
- 24
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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
@@ -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"
|
||||||
|
|||||||
@@ -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/"
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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=[])
|
||||||
@@ -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]
|
||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
@@ -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)
|
||||||
@@ -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
Reference in New Issue
Block a user