Author SHA1 Message Date
Dniel97 a0793aa13a diva: added clear set calculation + small improvements 2023-04-30 23:31:13 +02:00
Dniel97 7364181de1 Merge branch 'develop' into fork_develop 2023-04-30 20:39:35 +02:00
Hay1tsme 9d8762d3da Update 'readme.md' 2023-04-29 21:18:28 -04:00
Kevin Trocolli 238d437519 reformat with black in preperation for merge to master 2023-04-23 21:04:52 -04:00
Kevin Trocolli 9d23d59e43 add changelog 2023-04-23 21:04:15 -04:00
Kevin Trocolli f4ee4238d9 update readme 2023-04-23 20:51:23 -04:00
Kevin Trocolli b498e82bf8 dli: remove dot from version 2023-04-23 19:08:45 -04:00
Kevin Trocolli 0668488ccf update core example config 2023-04-23 19:06:44 -04:00
Kevin Trocolli 47f4aaddf8 allnet: add download order infrastructure 2023-04-23 19:00:30 -04:00
Kevin Trocolli 26c4bcb466 idz: Add requests, fix load_config_b 2023-04-23 13:26:44 -04:00
Kevin Trocolli d8c3ed5c01 Add support for initial d zero 2023-04-23 04:38:28 -04:00
Hay1tsme 58a088b9a4 wacca: add debug log for ticket use 2023-04-21 10:51:44 -04:00
Hay1tsme 190c41e03e wacca: added play mode counter for time free 2023-04-21 10:51:30 -04:00
Kevin Trocolli a30967e8d7 wacca: fix time free not saving, add counter to profile table 2023-04-20 09:46:18 -04:00
Kevin Trocolli 00b127361b wacca: enable time play 2023-04-20 02:27:09 -04:00
Kevin Trocolli 241f29e29c wacca: add a comment 2023-04-20 02:02:42 -04:00
Kevin Trocolli 4d6afd757f wacca: add helpers for gacha, event and friend info, fix settings not being applied correctly 2023-04-20 00:54:16 -04:00
Hay1tsme 68b0894e47 wacca: fix first play of the day calculation 2023-04-19 16:12:35 -04:00
Midorica 017ef1e224 Merge pull request 'maimai DX FESTiVAL support' (#17) from Dniel97/artemis:maimai_dx_festival into develop
Reviewed-on: https://gitea.tendokyu.moe/Hay1tsme/artemis/pulls/17
2023-04-19 18:13:31 +00:00
Hay1tsme 15433b681c mai2: fix logging in put_card 2023-04-19 11:26:33 -04:00
Hay1tsme b0042bc776 docs: update in accordance with new dbutils 2023-04-19 10:50:40 -04:00
Kevin Trocolli 469ead7a84 wacca: add previously disabled gates, for #15 2023-04-18 02:52:41 -04:00
Kevin Trocolli 0dc96f33e1 database: don't set schema version if autoupdate fails 2023-04-15 03:13:14 -04:00
Kevin Trocolli 4102ba21fc database: remove print 2023-04-15 03:07:15 -04:00
Kevin Trocolli 83d2151b6b dbutils: fix config loading incorrectly 2023-04-15 03:06:11 -04:00
Kevin Trocolli 9895068125 database: fix autoupdate 2023-04-15 01:31:52 -04:00
Kevin Trocolli 4419310086 fix schema versions for diva and ongeki 2023-04-15 01:31:40 -04:00
Kevin Trocolli a416fb09e1 dbutils: version can now be left black to auto-upgrade to latest 2023-04-15 00:13:04 -04:00
Kevin Trocolli baa885f674 Utils: exclude malformed game folders 2023-04-15 00:12:45 -04:00
Kevin Trocolli 0d5567c990 wacca: fix v4 upgrade scripts 2023-04-15 00:12:28 -04:00
Kevin Trocolli b1f9be0121 wacca: fix crash on 4th page of Reverse Gate, partially fixes #5 2023-04-14 23:47:31 -04:00
Kevin Trocolli dc3e3e1fb3 pokken: add constants, add stats to profile table 2023-04-14 02:51:28 -04:00
Kevin Trocolli 71c43a4a57 pokken: add_profile_points stub 2023-04-12 02:39:56 -04:00
Kevin Trocolli bd356af272 pokken: restructure database 2023-04-12 02:34:29 -04:00
Hay1tsme 68e25b9c5e pokken: add frontend stub 2023-04-11 11:40:05 -04:00
98 changed files with 2396 additions and 520 deletions
+1
View File
@@ -158,5 +158,6 @@ cert/*
!cert/server.pem
config/*
deliver/*
*.gz
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.Signature import PKCS1_v1_5
from time import strptime
from os import path
from core.config import CoreConfig
from core.utils import Utils
@@ -55,7 +56,7 @@ class AllnetServlet:
self.logger.error("No games detected!")
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:
enabled, uri, host = mod.index.get_allnet_info(
code, self.config, self.config_folder
@@ -106,7 +107,9 @@ class AllnetServlet:
return self.dict_to_http_form_string([vars(resp)])
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.host = f"{self.config.title.hostname}:{self.config.title.port}"
return self.dict_to_http_form_string([vars(resp)])
@@ -188,15 +191,51 @@ class AllnetServlet:
self.logger.error(e)
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()
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)])
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)])
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):
req_dict = self.billing_req_to_dict(request.content.getvalue())
request_ip = Utils.get_ip_addr(request)
@@ -419,7 +458,7 @@ class AllnetDownloadOrderRequest:
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.serial = serial
self.uri = uri
+6
View File
@@ -188,6 +188,12 @@ class AllnetConfig:
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:
def __init__(self, parent_config: "CoreConfig") -> None:
+72 -16
View File
@@ -1,5 +1,5 @@
import logging, coloredlogs
from typing import Optional
from typing import Optional, Dict, List
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy import create_engine
@@ -32,7 +32,7 @@ class Data:
self.arcade = ArcadeData(self.config, self.session)
self.card = CardData(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 = logging.Formatter(log_fmt_str)
@@ -71,7 +71,9 @@ class Data:
games = Utils.get_all_titles()
for game_dir, game_mod in games.items():
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)
metadata.create_all(self.__engine.connect())
@@ -84,8 +86,8 @@ class Data:
f"Could not load database schema from {game_dir} - {e}"
)
self.logger.info(f"Setting base_schema_ver to {self.schema_ver_latest}")
self.base.set_schema_ver(self.schema_ver_latest)
self.logger.info(f"Setting base_schema_ver to {self.current_schema_version}")
self.base.set_schema_ver(self.current_schema_version)
self.logger.info(
f"Setting user auto_incrememnt to {self.config.database.user_table_autoincrement_start}"
@@ -129,9 +131,32 @@ class Data:
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)
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:
self.logger.error(
@@ -263,17 +288,48 @@ class Data:
self.user.delete_user(user["id"])
def autoupgrade(self) -> None:
all_games = self.base.get_all_schema_vers()
if all_games is None:
all_game_versions = self.base.get_all_schema_vers()
if all_game_versions is None:
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()
update_ver = 1
for y in range(2, 100):
if os.path.exists(f"core/data/schema/versions/{game}_{y}_upgrade.sql"):
update_ver = y
else:
break
update_ver = int(x["version"])
latest_ver = all_games_list.get(game, 1)
if game == "CORE":
latest_ver = self.current_schema_version
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
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)
except SQLAlchemyError as e:
@@ -0,0 +1,9 @@
ALTER TABLE diva_profile
DROP cnp_cid,
DROP cnp_val,
DROP cnp_rr,
DROP cnp_sp,
DROP btn_se_eqp,
DROP sld_se_eqp,
DROP chn_sld_se_eqp,
DROP sldr_tch_se_eqp;
@@ -0,0 +1,9 @@
ALTER TABLE diva_profile
ADD cnp_cid INT NOT NULL DEFAULT -1,
ADD cnp_val INT NOT NULL DEFAULT -1,
ADD cnp_rr INT NOT NULL DEFAULT -1,
ADD cnp_sp VARCHAR(255) NOT NULL DEFAULT "",
ADD btn_se_eqp INT NOT NULL DEFAULT -1,
ADD sld_se_eqp INT NOT NULL DEFAULT -1,
ADD chn_sld_se_eqp INT NOT NULL DEFAULT -1,
ADD sldr_tch_se_eqp INT NOT NULL DEFAULT -1;
@@ -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)
self.game_list.append({"url": game_dir, "name": game_fe.nav_name})
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.putChild(b"gate", FE_Gate(cfg, self.environment))
+3 -9
View File
@@ -46,9 +46,7 @@ class MuchaServlet:
if enabled:
self.mucha_registry.append(game_cd)
self.logger.info(
f"Serving {len(self.mucha_registry)} games"
)
self.logger.info(f"Serving {len(self.mucha_registry)} games")
def handle_boardauth(self, request: Request, _: Dict) -> bytes:
req_dict = self.mucha_preprocess(request.content.getvalue())
@@ -62,9 +60,7 @@ class MuchaServlet:
req = MuchaAuthRequest(req_dict)
self.logger.debug(f"Mucha request {vars(req)}")
self.logger.info(
f"Boardauth request from {client_ip} for {req.gameVer}"
)
self.logger.info(f"Boardauth request from {client_ip} for {req.gameVer}")
if req.gameCd not in self.mucha_registry:
self.logger.warn(f"Unknown gameCd {req.gameCd}")
@@ -92,9 +88,7 @@ class MuchaServlet:
req = MuchaUpdateRequest(req_dict)
self.logger.debug(f"Mucha request {vars(req)}")
self.logger.info(
f"Updatecheck request from {client_ip} for {req.gameVer}"
)
self.logger.info(f"Updatecheck request from {client_ip} for {req.gameVer}")
if req.gameCd not in self.mucha_registry:
self.logger.warn(f"Unknown gameCd {req.gameCd}")
+8 -1
View File
@@ -16,6 +16,9 @@ class Utils:
if not dir.startswith("__"):
try:
mod = importlib.import_module(f"titles.{dir}")
if hasattr(mod, "game_codes") and hasattr(
mod, "index"
): # Minimum required to function
ret[dir] = mod
except ImportError as e:
@@ -25,4 +28,8 @@ class Utils:
@classmethod
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 argparse
import logging
from core.config import CoreConfig
from core.data import Data
from os import path, mkdir, access, W_OK
@@ -32,7 +33,9 @@ if __name__ == "__main__":
cfg = CoreConfig()
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):
mkdir(cfg.server.log_dir)
@@ -45,7 +48,6 @@ if __name__ == "__main__":
data = Data(cfg)
if args.action == "create":
data.create_database()
@@ -54,15 +56,22 @@ if __name__ == "__main__":
elif args.action == "upgrade" or args.action == "rollback":
if args.version is None:
data.logger.error("Must set game and version to migrate to")
exit(0)
data.logger.warn("No version set, upgrading to latest")
if args.game is None:
data.logger.info("No game set, upgrading core schema")
data.migrate_database("CORE", int(args.version), args.action)
data.logger.warn("No game set, upgrading core schema")
data.migrate_database(
"CORE",
int(args.version) if args.version is not None else None,
args.action,
)
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":
data.autoupgrade()
+5 -11
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:
```shell
python dbutils.py --game SDBT --version 2 upgrade
python dbutils.py --game SDBT --version 3 upgrade
python dbutils.py --game SDBT upgrade
```
## crossbeats REV.
@@ -134,7 +133,7 @@ crash without Events!**
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
python dbutils.py --game SDEZ --version 2 upgrade
python dbutils.py --game SDEZ upgrade
```
## Hatsune Miku Project Diva
@@ -175,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:
```shell
python dbutils.py --game SBZV --version 2 upgrade
python dbutils.py --game SBZV --version 3 upgrade
python dbutils.py --game SBZV --version 4 upgrade
python dbutils.py --game SBZV upgrade
```
## O.N.G.E.K.I.
@@ -225,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:
```shell
python dbutils.py --game SDDT --version 2 upgrade
python dbutils.py --game SDDT --version 3 upgrade
python dbutils.py --game SDDT --version 4 upgrade
python dbutils.py --game SDDT upgrade
```
## Card Maker
@@ -347,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:
```shell
python dbutils.py --game SDFE --version 2 upgrade
python dbutils.py --game SDFE --version 3 upgrade
python dbutils.py --game SDFE upgrade
```
+1
View File
@@ -32,6 +32,7 @@ allnet:
loglevel: "info"
port: 80
allow_online_updates: False
update_cfg_folder: ""
billing:
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
- 18
- 19
- 20
- 21
- 22
- 23
- 24
+16
View File
@@ -26,6 +26,22 @@ class HttpDispatcher(resource.Resource):
self.title = TitleServlet(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(
"allnet_ping",
"/naomitest.html",
+1 -2
View File
@@ -135,8 +135,7 @@ if __name__ == "__main__":
for dir, mod in titles.items():
if args.series in mod.game_codes:
handler = mod.reader(config, args.version,
bin_arg, opt_arg, args.extra)
handler = mod.reader(config, args.version, bin_arg, opt_arg, args.extra)
handler.read()
logger.info("Done")
+3 -1
View File
@@ -2,7 +2,7 @@
A network service emulator for games running SEGA'S ALL.NET service, and similar.
# 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
+ All versions up to New!! Plus
@@ -26,6 +26,8 @@ Games listed below have been tested and confirmed working. Only game versions ol
+ Lily R
+ Reverse
+ Pokken
+ Final Online
## Requirements
- 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] = {}
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:
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.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
def get_allnet_info(
@@ -167,11 +179,15 @@ class ChuniServlet:
else:
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"}')
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"}')
endpoint = self.hash_table[internal_ver][endpoint.lower()]
@@ -193,7 +209,11 @@ class ChuniServlet:
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(
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)
self.logger.info(
f"v{version} {endpoint} request from {client_ip}"
)
self.logger.info(f"v{version} {endpoint} request from {client_ip}")
self.logger.debug(req_data)
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:
ret = super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["romVersion"] = self.game_cfg.version.version(self.version)["rom"]
ret["gameSetting"]["dataVersion"] = self.game_cfg.version.version(self.version)["data"]
ret["gameSetting"]["romVersion"] = self.game_cfg.version.version(self.version)[
"rom"
]
ret["gameSetting"]["dataVersion"] = self.game_cfg.version.version(self.version)[
"data"
]
ret["gameSetting"][
"matchingUri"
] = 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
def get_login_bonus(
self, version: int, preset_id: int,
self,
version: int,
preset_id: int,
) -> Optional[List[Row]]:
sql = login_bonus.select(
and_(
+1 -3
View File
@@ -109,9 +109,7 @@ class CardMakerServlet:
req_data = json.loads(unzip)
self.logger.info(
f"v{version} {endpoint} request from {client_ip}"
)
self.logger.info(f"v{version} {endpoint} request from {client_ip}")
self.logger.debug(req_data)
func_to_find = "handle_" + inflection.underscore(endpoint) + "_request"
+1 -1
View File
@@ -90,7 +90,7 @@ class CardMakerReader(BaseReader):
"v2_00": ChuniConstants.VER_CHUNITHM_NEW,
"v2_05": ChuniConstants.VER_CHUNITHM_NEW_PLUS,
# 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):
+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}"
)
else:
self.logger.info(
f"Ready on port {self.game_cfg.server.port}"
)
self.logger.info(f"Ready on port {self.game_cfg.server.port}")
def render_POST(self, request: Request):
version = 0
+1 -1
View File
@@ -7,4 +7,4 @@ index = DivaServlet
database = DivaData
reader = DivaReader
game_codes = [DivaConstants.GAME_CODE]
current_schema_version = 1
current_schema_version = 5
+84 -12
View File
@@ -266,16 +266,17 @@ class DivaBase:
def handle_festa_info_request(self, data: Dict) -> Dict:
encoded = "&"
params = {
"fi_id": "1,-1",
"fi_name": f"{self.core_cfg.server.name} Opening,xxx",
"fi_kind": "0,0",
"fi_id": "1,2",
"fi_name": f"{self.core_cfg.server.name} Opening,Project DIVA Festa",
# 0=PINK, 1=GREEN
"fi_kind": "1,0",
"fi_difficulty": "-1,-1",
"fi_pv_id_lst": "ALL,ALL",
"fi_attr": "7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
"fi_add_vp": "20,0",
"fi_mul_vp": "1,1",
"fi_st": "2022-06-17 17:00:00.0,2014-07-08 18:10:11.0",
"fi_et": "2029-01-01 10:00:00.0,2014-07-08 18:10:11.0",
"fi_add_vp": "20,5",
"fi_mul_vp": "1,2",
"fi_st": "2019-01-01 00:00:00.0,2019-01-01 00:00:00.0",
"fi_et": "2029-01-01 00:00:00.0,2029-01-01 00:00:00.0",
"fi_lut": "{self.time_lut}",
}
@@ -401,10 +402,10 @@ class DivaBase:
response += f"&lv_pnt={profile['lv_pnt']}"
response += f"&vcld_pts={profile['vcld_pts']}"
response += f"&skn_eqp={profile['use_pv_skn_eqp']}"
response += f"&btn_se_eqp={profile['use_pv_btn_se_eqp']}"
response += f"&sld_se_eqp={profile['use_pv_sld_se_eqp']}"
response += f"&chn_sld_se_eqp={profile['use_pv_chn_sld_se_eqp']}"
response += f"&sldr_tch_se_eqp={profile['use_pv_sldr_tch_se_eqp']}"
response += f"&btn_se_eqp={profile['btn_se_eqp']}"
response += f"&sld_se_eqp={profile['sld_se_eqp']}"
response += f"&chn_sld_se_eqp={profile['chn_sld_se_eqp']}"
response += f"&sldr_tch_se_eqp={profile['sldr_tch_se_eqp']}"
response += f"&passwd_stat={profile['passwd_stat']}"
# Store stuff to add to rework
@@ -478,6 +479,21 @@ class DivaBase:
response += f"&dsp_clr_sts={profile['dsp_clr_sts']}"
response += f"&rgo_sts={profile['rgo_sts']}"
# Contest progress
response += f"&cv_cid=-1,-1,-1,-1"
response += f"&cv_sc=-1,-1,-1,-1"
response += f"&cv_bv=-1,-1,-1,-1"
response += f"&cv_bv=-1,-1,-1,-1"
response += f"&cv_bf=-1,-1,-1,-1"
# Contest now playing id, return -1 if no current playing contest
response += f"&cnp_cid={profile['cnp_cid']}"
response += f"&cnp_val={profile['cnp_val']}"
# border can be 0=bronzem 1=silver, 2=gold
response += f"&cnp_rr={profile['cnp_rr']}"
# only show contest specifier if it is not empty
response += f"&cnp_sp={profile['cnp_sp']}" if profile["cnp_sp"] != "" else ""
# To be fully fixed
if "my_qst_id" not in profile:
response += f"&my_qst_id=-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1"
@@ -488,7 +504,63 @@ class DivaBase:
response += f"&my_qst_prgrs=0,0,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1"
response += f"&my_qst_et=2022-06-19%2010%3A28%3A52.0,2022-06-19%2010%3A28%3A52.0,2022-06-19%2010%3A28%3A52.0,2100-01-01%2008%3A59%3A59.0,2100-01-01%2008%3A59%3A59.0,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx"
response += f"&clr_sts=0,0,0,0,0,0,0,0,56,52,35,6,6,3,1,0,0,0,0,0"
# define a helper class to store all counts for clear, great,
# excellent and perfect
class ClearSet:
def __init__(self):
self.clear = 0
self.great = 0
self.excellent = 0
self.perfect = 0
# create a dict to store the ClearSets per difficulty
clear_set_dict = {
0: ClearSet(), # easy
1: ClearSet(), # normal
2: ClearSet(), # hard
3: ClearSet(), # extreme
4: ClearSet(), # exExtreme
}
# get clear status from user scores
pv_records = self.data.score.get_best_scores(data["pd_id"])
clear_status = "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
if pv_records is not None:
for score in pv_records:
if score["edition"] == 0:
# cheap and standard both count to "clear"
if score["clr_kind"] in {1, 2}:
clear_set_dict[score["difficulty"]].clear += 1
elif score["clr_kind"] == 3:
clear_set_dict[score["difficulty"]].great += 1
elif score["clr_kind"] == 4:
clear_set_dict[score["difficulty"]].excellent += 1
elif score["clr_kind"] == 5:
clear_set_dict[score["difficulty"]].perfect += 1
else:
# 4=ExExtreme
if score["clr_kind"] in {1, 2}:
clear_set_dict[4].clear += 1
elif score["clr_kind"] == 3:
clear_set_dict[4].great += 1
elif score["clr_kind"] == 4:
clear_set_dict[4].excellent += 1
elif score["clr_kind"] == 5:
clear_set_dict[4].perfect += 1
# now add all values to a list
clear_list = []
for clear_set in clear_set_dict.values():
clear_list.append(clear_set.clear)
clear_list.append(clear_set.great)
clear_list.append(clear_set.excellent)
clear_list.append(clear_set.perfect)
clear_status = ",".join(map(str, clear_list))
response += f"&clr_sts={clear_status}"
# Store stuff to add to rework
response += f"&mdl_eqp_tm={self.time_lut}"
+8
View File
@@ -34,9 +34,17 @@ profile = Table(
Column("use_pv_sld_se_eqp", Boolean, nullable=False, server_default="0"),
Column("use_pv_chn_sld_se_eqp", Boolean, nullable=False, server_default="0"),
Column("use_pv_sldr_tch_se_eqp", Boolean, nullable=False, server_default="0"),
Column("btn_se_eqp", Integer, nullable=False, server_default="-1"),
Column("sld_se_eqp", Integer, nullable=False, server_default="-1"),
Column("chn_sld_se_eqp", Integer, nullable=False, server_default="-1"),
Column("sldr_tch_se_eqp", Integer, nullable=False, server_default="-1"),
Column("nxt_pv_id", Integer, nullable=False, server_default="708"),
Column("nxt_dffclty", Integer, nullable=False, server_default="2"),
Column("nxt_edtn", Integer, nullable=False, server_default="0"),
Column("cnp_cid", Integer, nullable=False, server_default="-1"),
Column("cnp_val", Integer, nullable=False, server_default="-1"),
Column("cnp_rr", Integer, nullable=False, server_default="-1"),
Column("cnp_sp", String(255), nullable=False, server_default=""),
Column("dsp_clr_brdr", Integer, nullable=False, server_default="7"),
Column("dsp_intrm_rnk", Integer, nullable=False, server_default="1"),
Column("dsp_clr_sts", Integer, nullable=False, server_default="1"),
+5 -4
View File
@@ -3,6 +3,7 @@ from sqlalchemy.types import Integer, String, TIMESTAMP, JSON, Boolean
from sqlalchemy.schema import ForeignKey
from sqlalchemy.sql import func, select
from sqlalchemy.dialects.mysql import insert
from sqlalchemy.engine import Row
from typing import Optional, List, Dict, Any
from core.data.schema import BaseData, metadata
@@ -167,7 +168,7 @@ class DivaScoreData(BaseData):
def get_best_user_score(
self, user_id: int, pv_id: int, difficulty: int, edition: int
) -> Optional[Dict]:
) -> Optional[Row]:
sql = score.select(
and_(
score.c.user == user_id,
@@ -184,7 +185,7 @@ class DivaScoreData(BaseData):
def get_top3_scores(
self, pv_id: int, difficulty: int, edition: int
) -> Optional[List[Dict]]:
) -> Optional[List[Row]]:
sql = (
score.select(
and_(
@@ -204,7 +205,7 @@ class DivaScoreData(BaseData):
def get_global_ranking(
self, user_id: int, pv_id: int, difficulty: int, edition: int
) -> Optional[List]:
) -> Optional[List[Row]]:
# get the subquery max score of a user with pv_id, difficulty and
# edition
sql_sub = (
@@ -231,7 +232,7 @@ class DivaScoreData(BaseData):
return None
return result.fetchone()
def get_best_scores(self, user_id: int) -> Optional[List]:
def get_best_scores(self, user_id: int) -> Optional[List[Row]]:
sql = score.select(score.c.user == user_id)
result = self.execute(sql)
+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)

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