forked from Hay1tsme/artemis
Compare commits
23 Commits
Author | SHA1 | Date |
---|---|---|
Hay1tsme | 42ed222095 | |
Hay1tsme | d172e5582b | |
Hay1tsme | 9766e3ab78 | |
Hay1tsme | b34b441ba8 | |
Hay1tsme | 8149f09a40 | |
Hay1tsme | cad523dfce | |
Hay1tsme | 8b9771b5af | |
Hay1tsme | 989c080657 | |
Hay1tsme | dcff8adbab | |
Hay1tsme | e3b1addce6 | |
Hay1tsme | b6f43d887a | |
Hay1tsme | efd8f86e48 | |
Hay1tsme | d0242b456d | |
Hay1tsme | 7bb8c2c80c | |
Hay1tsme | 6d1855a6bc | |
Hay1tsme | 8d94d25893 | |
Hay1tsme | ae6dcb68df | |
Hay1tsme | 3b6fc6618c | |
Hay1tsme | deeac1d8db | |
Midorica | 6ad5194bb8 | |
Dniel97 | a0793aa13a | |
Dniel97 | 7364181de1 | |
Hay1tsme | 9d8762d3da |
|
@ -0,0 +1,21 @@
|
||||||
|
FROM python:3.9.15-slim-bullseye
|
||||||
|
|
||||||
|
RUN apt update && apt install default-libmysqlclient-dev build-essential libtk nodejs npm -y
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY requirements.txt requirements.txt
|
||||||
|
RUN pip3 install -r requirements.txt
|
||||||
|
RUN npm i -g nodemon
|
||||||
|
|
||||||
|
COPY entrypoint.sh entrypoint.sh
|
||||||
|
RUN chmod +x entrypoint.sh
|
||||||
|
|
||||||
|
COPY index.py index.py
|
||||||
|
COPY dbutils.py dbutils.py
|
||||||
|
ADD core core
|
||||||
|
ADD titles titles
|
||||||
|
ADD config config
|
||||||
|
ADD log log
|
||||||
|
ADD cert cert
|
||||||
|
|
||||||
|
ENTRYPOINT [ "/app/entrypoint.sh" ]
|
|
@ -194,7 +194,7 @@ class AllnetServlet:
|
||||||
self.logger.info(
|
self.logger.info(
|
||||||
f"DownloadOrder from {request_ip} -> {req.game_id} v{req.ver} serial {req.serial}"
|
f"DownloadOrder from {request_ip} -> {req.game_id} v{req.ver} serial {req.serial}"
|
||||||
)
|
)
|
||||||
resp = AllnetDownloadOrderResponse()
|
resp = AllnetDownloadOrderResponse(serial=req.serial)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
not self.config.allnet.allow_online_updates
|
not self.config.allnet.allow_online_updates
|
||||||
|
@ -249,14 +249,18 @@ class AllnetServlet:
|
||||||
signer = PKCS1_v1_5.new(rsa)
|
signer = PKCS1_v1_5.new(rsa)
|
||||||
digest = SHA.new()
|
digest = SHA.new()
|
||||||
|
|
||||||
kc_playlimit = int(req_dict[0]["playlimit"])
|
try:
|
||||||
kc_nearfull = int(req_dict[0]["nearfull"])
|
kc_playlimit = int(req_dict[0]["playlimit"])
|
||||||
kc_billigtype = int(req_dict[0]["billingtype"])
|
kc_nearfull = int(req_dict[0]["nearfull"])
|
||||||
kc_playcount = int(req_dict[0]["playcnt"])
|
kc_billigtype = int(req_dict[0]["billingtype"])
|
||||||
kc_serial: str = req_dict[0]["keychipid"]
|
kc_playcount = int(req_dict[0]["playcnt"])
|
||||||
kc_game: str = req_dict[0]["gameid"]
|
kc_serial: str = req_dict[0]["keychipid"]
|
||||||
kc_date = strptime(req_dict[0]["date"], "%Y%m%d%H%M%S")
|
kc_game: str = req_dict[0]["gameid"]
|
||||||
kc_serial_bytes = kc_serial.encode()
|
kc_date = strptime(req_dict[0]["date"], "%Y%m%d%H%M%S")
|
||||||
|
kc_serial_bytes = kc_serial.encode()
|
||||||
|
|
||||||
|
except KeyError as e:
|
||||||
|
return f"result=5&linelimit=&message={e} field is missing".encode()
|
||||||
|
|
||||||
machine = self.data.arcade.get_machine(kc_serial)
|
machine = self.data.arcade.get_machine(kc_serial)
|
||||||
if machine is None and not self.config.server.allow_unregistered_serials:
|
if machine is None and not self.config.server.allow_unregistered_serials:
|
||||||
|
@ -426,6 +430,7 @@ class AllnetPowerOnResponse3:
|
||||||
|
|
||||||
class AllnetPowerOnResponse2:
|
class AllnetPowerOnResponse2:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
|
time = datetime.now(tz=pytz.timezone("Asia/Tokyo"))
|
||||||
self.stat = 1
|
self.stat = 1
|
||||||
self.uri = ""
|
self.uri = ""
|
||||||
self.host = ""
|
self.host = ""
|
||||||
|
@ -438,14 +443,14 @@ class AllnetPowerOnResponse2:
|
||||||
self.region_name2 = "Y"
|
self.region_name2 = "Y"
|
||||||
self.region_name3 = "Z"
|
self.region_name3 = "Z"
|
||||||
self.country = "JPN"
|
self.country = "JPN"
|
||||||
self.year = datetime.now().year
|
self.year = time.year
|
||||||
self.month = datetime.now().month
|
self.month = time.month
|
||||||
self.day = datetime.now().day
|
self.day = time.day
|
||||||
self.hour = datetime.now().hour
|
self.hour = time.hour
|
||||||
self.minute = datetime.now().minute
|
self.minute = time.minute
|
||||||
self.second = datetime.now().second
|
self.second = time.second
|
||||||
self.setting = "1"
|
self.setting = "1"
|
||||||
self.timezone = "+0900"
|
self.timezone = "+09:00"
|
||||||
self.res_class = "PowerOnResponseV2"
|
self.res_class = "PowerOnResponseV2"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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,26 @@
|
||||||
|
DELETE FROM mai2_static_event WHERE version < 13;
|
||||||
|
UPDATE mai2_static_event SET version = version - 13 WHERE version >= 13;
|
||||||
|
|
||||||
|
DELETE FROM mai2_static_music WHERE version < 13;
|
||||||
|
UPDATE mai2_static_music SET version = version - 13 WHERE version >= 13;
|
||||||
|
|
||||||
|
DELETE FROM mai2_static_ticket WHERE version < 13;
|
||||||
|
UPDATE mai2_static_ticket SET version = version - 13 WHERE version >= 13;
|
||||||
|
|
||||||
|
DELETE FROM mai2_static_cards WHERE version < 13;
|
||||||
|
UPDATE mai2_static_cards SET version = version - 13 WHERE version >= 13;
|
||||||
|
|
||||||
|
DELETE FROM mai2_profile_detail WHERE version < 13;
|
||||||
|
UPDATE mai2_profile_detail SET version = version - 13 WHERE version >= 13;
|
||||||
|
|
||||||
|
DELETE FROM mai2_profile_extend WHERE version < 13;
|
||||||
|
UPDATE mai2_profile_extend SET version = version - 13 WHERE version >= 13;
|
||||||
|
|
||||||
|
DELETE FROM mai2_profile_option WHERE version < 13;
|
||||||
|
UPDATE mai2_profile_option SET version = version - 13 WHERE version >= 13;
|
||||||
|
|
||||||
|
DELETE FROM mai2_profile_ghost WHERE version < 13;
|
||||||
|
UPDATE mai2_profile_ghost SET version = version - 13 WHERE version >= 13;
|
||||||
|
|
||||||
|
DELETE FROM mai2_profile_rating WHERE version < 13;
|
||||||
|
UPDATE mai2_profile_rating SET version = version - 13 WHERE version >= 13;
|
|
@ -0,0 +1,17 @@
|
||||||
|
UPDATE mai2_static_event SET version = version + 13 WHERE version < 1000;
|
||||||
|
|
||||||
|
UPDATE mai2_static_music SET version = version + 13 WHERE version < 1000;
|
||||||
|
|
||||||
|
UPDATE mai2_static_ticket SET version = version + 13 WHERE version < 1000;
|
||||||
|
|
||||||
|
UPDATE mai2_static_cards SET version = version + 13 WHERE version < 1000;
|
||||||
|
|
||||||
|
UPDATE mai2_profile_detail SET version = version + 13 WHERE version < 1000;
|
||||||
|
|
||||||
|
UPDATE mai2_profile_extend SET version = version + 13 WHERE version < 1000;
|
||||||
|
|
||||||
|
UPDATE mai2_profile_option SET version = version + 13 WHERE version < 1000;
|
||||||
|
|
||||||
|
UPDATE mai2_profile_ghost SET version = version + 13 WHERE version < 1000;
|
||||||
|
|
||||||
|
UPDATE mai2_profile_rating SET version = version + 13 WHERE version < 1000;
|
|
@ -0,0 +1,57 @@
|
||||||
|
version: "3.9"
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
hostname: ma.app
|
||||||
|
build: .
|
||||||
|
volumes:
|
||||||
|
- ./aime:/app/aime
|
||||||
|
|
||||||
|
environment:
|
||||||
|
CFG_DEV: 1
|
||||||
|
CFG_CORE_SERVER_HOSTNAME: 0.0.0.0
|
||||||
|
CFG_CORE_DATABASE_HOST: ma.db
|
||||||
|
CFG_CORE_MEMCACHED_HOSTNAME: ma.memcached
|
||||||
|
CFG_CORE_AIMEDB_KEY: keyhere
|
||||||
|
CFG_CHUNI_SERVER_LOGLEVEL: debug
|
||||||
|
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "8443:8443"
|
||||||
|
- "22345:22345"
|
||||||
|
|
||||||
|
- "8080:8080"
|
||||||
|
- "8090:8090"
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
db:
|
||||||
|
hostname: ma.db
|
||||||
|
image: mysql:8.0.31-debian
|
||||||
|
environment:
|
||||||
|
MYSQL_DATABASE: aime
|
||||||
|
MYSQL_USER: aime
|
||||||
|
MYSQL_PASSWORD: aime
|
||||||
|
MYSQL_ROOT_PASSWORD: AimeRootPassword
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
|
||||||
|
memcached:
|
||||||
|
hostname: ma.memcached
|
||||||
|
image: memcached:1.6.17-bullseye
|
||||||
|
|
||||||
|
phpmyadmin:
|
||||||
|
hostname: ma.phpmyadmin
|
||||||
|
image: phpmyadmin:latest
|
||||||
|
environment:
|
||||||
|
PMA_HOSTS: ma.db
|
||||||
|
PMA_USER: root
|
||||||
|
PMA_PASSWORD: AimeRootPassword
|
||||||
|
APACHE_PORT: 8080
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
|
|
@ -105,28 +105,49 @@ Config file is located in `config/cxb.yaml`.
|
||||||
|
|
||||||
### SDEZ
|
### SDEZ
|
||||||
|
|
||||||
| Version ID | Version Name |
|
| Game Code | Version ID | Version Name |
|
||||||
|------------|-------------------------|
|
|-----------|------------|-------------------------|
|
||||||
| 0 | maimai DX |
|
| SDEZ | 0 | maimai DX |
|
||||||
| 1 | maimai DX PLUS |
|
| SDEZ | 1 | maimai DX PLUS |
|
||||||
| 2 | maimai DX Splash |
|
| SDEZ | 2 | maimai DX Splash |
|
||||||
| 3 | maimai DX Splash PLUS |
|
| SDEZ | 3 | maimai DX Splash PLUS |
|
||||||
| 4 | maimai DX Universe |
|
| SDEZ | 4 | maimai DX Universe |
|
||||||
| 5 | maimai DX Universe PLUS |
|
| SDEZ | 5 | maimai DX Universe PLUS |
|
||||||
| 6 | maimai DX Festival |
|
| SDEZ | 6 | maimai DX Festival |
|
||||||
|
|
||||||
|
For versions pre-dx
|
||||||
|
| Game Code | Version ID | Version Name |
|
||||||
|
|-----------|------------|----------------------|
|
||||||
|
| SBXL | 1000 | maimai |
|
||||||
|
| SBXL | 1001 | maimai PLUS |
|
||||||
|
| SBZF | 1002 | maimai GreeN |
|
||||||
|
| SBZF | 1003 | maimai GreeN PLUS |
|
||||||
|
| SDBM | 1004 | maimai ORANGE |
|
||||||
|
| SDBM | 1005 | maimai ORANGE PLUS |
|
||||||
|
| SDCQ | 1006 | maimai PiNK |
|
||||||
|
| SDCQ | 1007 | maimai PiNK PLUS |
|
||||||
|
| SDDK | 1008 | maimai MURASAKI |
|
||||||
|
| SDDK | 1009 | maimai MURASAKI PLUS |
|
||||||
|
| SDDZ | 1010 | maimai MILK |
|
||||||
|
| SDDZ | 1011 | maimai MILK PLUS |
|
||||||
|
| SDEY | 1012 | maimai FiNALE |
|
||||||
|
|
||||||
### Importer
|
### Importer
|
||||||
|
|
||||||
In order to use the importer locate your game installation folder and execute:
|
In order to use the importer locate your game installation folder and execute:
|
||||||
|
DX:
|
||||||
```shell
|
```shell
|
||||||
python read.py --series SDEZ --version <version ID> --binfolder /path/to/game/folder --optfolder /path/to/game/option/folder
|
python read.py --series <Game Code> --version <Version ID> --binfolder /path/to/StreamingAssets --optfolder /path/to/game/option/folder
|
||||||
|
```
|
||||||
|
Pre-DX:
|
||||||
|
```shell
|
||||||
|
python read.py --series <Game Code> --version <Version ID> --binfolder /path/to/data --optfolder /path/to/patch/data
|
||||||
```
|
```
|
||||||
|
|
||||||
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
|
The importer for maimai Pre-DX will import Events and Music. Not all games will have patch data. Milk - Finale have file encryption, and need an AES key. That key is not provided by the developers. For games that do use encryption, provide the key, as a hex string, with the `--extra` flag. Ex `--extra 00112233445566778899AABBCCDDEEFF`
|
||||||
crash without Events!**
|
|
||||||
|
**Important: It is required to use the importer because some games may not function properly or even crash without Events!**
|
||||||
|
|
||||||
### Database upgrade
|
### Database upgrade
|
||||||
|
|
||||||
|
@ -135,6 +156,7 @@ Always make sure your database (tables) are up-to-date, to do so go to the `core
|
||||||
```shell
|
```shell
|
||||||
python dbutils.py --game SDEZ upgrade
|
python dbutils.py --game SDEZ upgrade
|
||||||
```
|
```
|
||||||
|
Pre-Dx uses the same database as DX, so only upgrade using the SDEZ game code!
|
||||||
|
|
||||||
## Hatsune Miku Project Diva
|
## Hatsune Miku Project Diva
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [[ -z "${CFG_DEV}" ]]; then
|
||||||
|
echo Production mode
|
||||||
|
python3 index.py
|
||||||
|
else
|
||||||
|
echo Development mode
|
||||||
|
python3 dbutils.py create
|
||||||
|
nodemon -w aime --legacy-watch index.py
|
||||||
|
fi
|
||||||
|
|
2
index.py
2
index.py
|
@ -42,7 +42,7 @@ class HttpDispatcher(resource.Resource):
|
||||||
conditions=dict(method=["POST"]),
|
conditions=dict(method=["POST"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
self.map_post.connect(
|
self.map_get.connect(
|
||||||
"allnet_ping",
|
"allnet_ping",
|
||||||
"/naomitest.html",
|
"/naomitest.html",
|
||||||
controller="allnet",
|
controller="allnet",
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
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
|
||||||
|
|
||||||
|
|
|
@ -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 = 4
|
current_schema_version = 5
|
||||||
|
|
|
@ -266,16 +266,17 @@ class DivaBase:
|
||||||
def handle_festa_info_request(self, data: Dict) -> Dict:
|
def handle_festa_info_request(self, data: Dict) -> Dict:
|
||||||
encoded = "&"
|
encoded = "&"
|
||||||
params = {
|
params = {
|
||||||
"fi_id": "1,-1",
|
"fi_id": "1,2",
|
||||||
"fi_name": f"{self.core_cfg.server.name} Opening,xxx",
|
"fi_name": f"{self.core_cfg.server.name} Opening,Project DIVA Festa",
|
||||||
"fi_kind": "0,0",
|
# 0=PINK, 1=GREEN
|
||||||
|
"fi_kind": "1,0",
|
||||||
"fi_difficulty": "-1,-1",
|
"fi_difficulty": "-1,-1",
|
||||||
"fi_pv_id_lst": "ALL,ALL",
|
"fi_pv_id_lst": "ALL,ALL",
|
||||||
"fi_attr": "7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
|
"fi_attr": "7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
|
||||||
"fi_add_vp": "20,0",
|
"fi_add_vp": "20,5",
|
||||||
"fi_mul_vp": "1,1",
|
"fi_mul_vp": "1,2",
|
||||||
"fi_st": "2022-06-17 17:00:00.0,2014-07-08 18:10:11.0",
|
"fi_st": "2019-01-01 00:00:00.0,2019-01-01 00:00:00.0",
|
||||||
"fi_et": "2029-01-01 10:00:00.0,2014-07-08 18:10:11.0",
|
"fi_et": "2029-01-01 00:00:00.0,2029-01-01 00:00:00.0",
|
||||||
"fi_lut": "{self.time_lut}",
|
"fi_lut": "{self.time_lut}",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -401,10 +402,10 @@ class DivaBase:
|
||||||
response += f"&lv_pnt={profile['lv_pnt']}"
|
response += f"&lv_pnt={profile['lv_pnt']}"
|
||||||
response += f"&vcld_pts={profile['vcld_pts']}"
|
response += f"&vcld_pts={profile['vcld_pts']}"
|
||||||
response += f"&skn_eqp={profile['use_pv_skn_eqp']}"
|
response += f"&skn_eqp={profile['use_pv_skn_eqp']}"
|
||||||
response += f"&btn_se_eqp={profile['use_pv_btn_se_eqp']}"
|
response += f"&btn_se_eqp={profile['btn_se_eqp']}"
|
||||||
response += f"&sld_se_eqp={profile['use_pv_sld_se_eqp']}"
|
response += f"&sld_se_eqp={profile['sld_se_eqp']}"
|
||||||
response += f"&chn_sld_se_eqp={profile['use_pv_chn_sld_se_eqp']}"
|
response += f"&chn_sld_se_eqp={profile['chn_sld_se_eqp']}"
|
||||||
response += f"&sldr_tch_se_eqp={profile['use_pv_sldr_tch_se_eqp']}"
|
response += f"&sldr_tch_se_eqp={profile['sldr_tch_se_eqp']}"
|
||||||
response += f"&passwd_stat={profile['passwd_stat']}"
|
response += f"&passwd_stat={profile['passwd_stat']}"
|
||||||
|
|
||||||
# Store stuff to add to rework
|
# Store stuff to add to rework
|
||||||
|
@ -478,6 +479,21 @@ class DivaBase:
|
||||||
response += f"&dsp_clr_sts={profile['dsp_clr_sts']}"
|
response += f"&dsp_clr_sts={profile['dsp_clr_sts']}"
|
||||||
response += f"&rgo_sts={profile['rgo_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
|
# To be fully fixed
|
||||||
if "my_qst_id" not in profile:
|
if "my_qst_id" not in profile:
|
||||||
response += f"&my_qst_id=-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1"
|
response += f"&my_qst_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_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"&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
|
# Store stuff to add to rework
|
||||||
response += f"&mdl_eqp_tm={self.time_lut}"
|
response += f"&mdl_eqp_tm={self.time_lut}"
|
||||||
|
|
|
@ -34,9 +34,17 @@ profile = Table(
|
||||||
Column("use_pv_sld_se_eqp", Boolean, nullable=False, server_default="0"),
|
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_chn_sld_se_eqp", Boolean, nullable=False, server_default="0"),
|
||||||
Column("use_pv_sldr_tch_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_pv_id", Integer, nullable=False, server_default="708"),
|
||||||
Column("nxt_dffclty", Integer, nullable=False, server_default="2"),
|
Column("nxt_dffclty", Integer, nullable=False, server_default="2"),
|
||||||
Column("nxt_edtn", Integer, nullable=False, server_default="0"),
|
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_clr_brdr", Integer, nullable=False, server_default="7"),
|
||||||
Column("dsp_intrm_rnk", Integer, nullable=False, server_default="1"),
|
Column("dsp_intrm_rnk", Integer, nullable=False, server_default="1"),
|
||||||
Column("dsp_clr_sts", Integer, nullable=False, server_default="1"),
|
Column("dsp_clr_sts", Integer, nullable=False, server_default="1"),
|
||||||
|
|
|
@ -3,6 +3,7 @@ from sqlalchemy.types import Integer, String, TIMESTAMP, JSON, Boolean
|
||||||
from sqlalchemy.schema import ForeignKey
|
from sqlalchemy.schema import ForeignKey
|
||||||
from sqlalchemy.sql import func, select
|
from sqlalchemy.sql import func, select
|
||||||
from sqlalchemy.dialects.mysql import insert
|
from sqlalchemy.dialects.mysql import insert
|
||||||
|
from sqlalchemy.engine import Row
|
||||||
from typing import Optional, List, Dict, Any
|
from typing import Optional, List, Dict, Any
|
||||||
|
|
||||||
from core.data.schema import BaseData, metadata
|
from core.data.schema import BaseData, metadata
|
||||||
|
@ -167,7 +168,7 @@ class DivaScoreData(BaseData):
|
||||||
|
|
||||||
def get_best_user_score(
|
def get_best_user_score(
|
||||||
self, user_id: int, pv_id: int, difficulty: int, edition: int
|
self, user_id: int, pv_id: int, difficulty: int, edition: int
|
||||||
) -> Optional[Dict]:
|
) -> Optional[Row]:
|
||||||
sql = score.select(
|
sql = score.select(
|
||||||
and_(
|
and_(
|
||||||
score.c.user == user_id,
|
score.c.user == user_id,
|
||||||
|
@ -184,7 +185,7 @@ class DivaScoreData(BaseData):
|
||||||
|
|
||||||
def get_top3_scores(
|
def get_top3_scores(
|
||||||
self, pv_id: int, difficulty: int, edition: int
|
self, pv_id: int, difficulty: int, edition: int
|
||||||
) -> Optional[List[Dict]]:
|
) -> Optional[List[Row]]:
|
||||||
sql = (
|
sql = (
|
||||||
score.select(
|
score.select(
|
||||||
and_(
|
and_(
|
||||||
|
@ -204,7 +205,7 @@ class DivaScoreData(BaseData):
|
||||||
|
|
||||||
def get_global_ranking(
|
def get_global_ranking(
|
||||||
self, user_id: int, pv_id: int, difficulty: int, edition: int
|
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
|
# get the subquery max score of a user with pv_id, difficulty and
|
||||||
# edition
|
# edition
|
||||||
sql_sub = (
|
sql_sub = (
|
||||||
|
@ -231,7 +232,7 @@ class DivaScoreData(BaseData):
|
||||||
return None
|
return None
|
||||||
return result.fetchone()
|
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)
|
sql = score.select(score.c.user == user_id)
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
|
|
|
@ -6,5 +6,14 @@ from titles.mai2.read import Mai2Reader
|
||||||
index = Mai2Servlet
|
index = Mai2Servlet
|
||||||
database = Mai2Data
|
database = Mai2Data
|
||||||
reader = Mai2Reader
|
reader = Mai2Reader
|
||||||
game_codes = [Mai2Constants.GAME_CODE]
|
game_codes = [
|
||||||
|
Mai2Constants.GAME_CODE_DX,
|
||||||
|
Mai2Constants.GAME_CODE_FINALE,
|
||||||
|
Mai2Constants.GAME_CODE_MILK,
|
||||||
|
Mai2Constants.GAME_CODE_MURASAKI,
|
||||||
|
Mai2Constants.GAME_CODE_PINK,
|
||||||
|
Mai2Constants.GAME_CODE_ORANGE,
|
||||||
|
Mai2Constants.GAME_CODE_GREEN,
|
||||||
|
Mai2Constants.GAME_CODE,
|
||||||
|
]
|
||||||
current_schema_version = 4
|
current_schema_version = 4
|
||||||
|
|
|
@ -12,30 +12,32 @@ class Mai2Base:
|
||||||
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
||||||
self.core_config = cfg
|
self.core_config = cfg
|
||||||
self.game_config = game_cfg
|
self.game_config = game_cfg
|
||||||
self.game = Mai2Constants.GAME_CODE
|
self.version = Mai2Constants.VER_MAIMAI
|
||||||
self.version = Mai2Constants.VER_MAIMAI_DX
|
|
||||||
self.data = Mai2Data(cfg)
|
self.data = Mai2Data(cfg)
|
||||||
self.logger = logging.getLogger("mai2")
|
self.logger = logging.getLogger("mai2")
|
||||||
|
self.can_deliver = False
|
||||||
|
self.can_usbdl = False
|
||||||
|
self.old_server = ""
|
||||||
|
|
||||||
|
if self.core_config.server.is_develop and self.core_config.title.port > 0:
|
||||||
|
self.old_server = f"http://{self.core_config.title.hostname}:{self.core_config.title.port}/SDEY/197/"
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.old_server = f"http://{self.core_config.title.hostname}/SDEY/197/"
|
||||||
|
|
||||||
def handle_get_game_setting_api_request(self, data: Dict):
|
def handle_get_game_setting_api_request(self, data: Dict):
|
||||||
reboot_start = date.strftime(
|
|
||||||
datetime.now() + timedelta(hours=3), Mai2Constants.DATE_TIME_FORMAT
|
|
||||||
)
|
|
||||||
reboot_end = date.strftime(
|
|
||||||
datetime.now() + timedelta(hours=4), Mai2Constants.DATE_TIME_FORMAT
|
|
||||||
)
|
|
||||||
return {
|
return {
|
||||||
"gameSetting": {
|
"gameSetting": {
|
||||||
"isMaintenance": "false",
|
"isMaintenance": "false",
|
||||||
"requestInterval": 10,
|
"requestInterval": 10,
|
||||||
"rebootStartTime": reboot_start,
|
"rebootStartTime": "2020-01-01 07:00:00.0",
|
||||||
"rebootEndTime": reboot_end,
|
"rebootEndTime": "2020-01-01 07:59:59.0",
|
||||||
"movieUploadLimit": 10000,
|
"movieUploadLimit": 10000,
|
||||||
"movieStatus": 0,
|
"movieStatus": 0,
|
||||||
"movieServerUri": "",
|
"movieServerUri": "",
|
||||||
"deliverServerUri": "",
|
"deliverServerUri": self.old_server + "deliver/" if self.can_deliver and self.game_config.deliver.enable else "",
|
||||||
"oldServerUri": "",
|
"oldServerUri": self.old_server + "old",
|
||||||
"usbDlServerUri": "",
|
"usbDlServerUri": self.old_server + "usbdl/" if self.can_deliver and self.game_config.deliver.udbdl_enable else "",
|
||||||
"rebootInterval": 0,
|
"rebootInterval": 0,
|
||||||
},
|
},
|
||||||
"isAouAccession": "true",
|
"isAouAccession": "true",
|
||||||
|
@ -51,7 +53,7 @@ class Mai2Base:
|
||||||
def handle_get_game_event_api_request(self, data: Dict) -> Dict:
|
def handle_get_game_event_api_request(self, data: Dict) -> Dict:
|
||||||
events = self.data.static.get_enabled_events(self.version)
|
events = self.data.static.get_enabled_events(self.version)
|
||||||
events_lst = []
|
events_lst = []
|
||||||
if events is None:
|
if events is None or not events:
|
||||||
self.logger.warn("No enabled events, did you run the reader?")
|
self.logger.warn("No enabled events, did you run the reader?")
|
||||||
return {"type": data["type"], "length": 0, "gameEventList": []}
|
return {"type": data["type"], "length": 0, "gameEventList": []}
|
||||||
|
|
||||||
|
@ -121,28 +123,20 @@ class Mai2Base:
|
||||||
"userId": data["userId"],
|
"userId": data["userId"],
|
||||||
"userName": profile["userName"],
|
"userName": profile["userName"],
|
||||||
"isLogin": False,
|
"isLogin": False,
|
||||||
"lastGameId": profile["lastGameId"],
|
|
||||||
"lastDataVersion": profile["lastDataVersion"],
|
"lastDataVersion": profile["lastDataVersion"],
|
||||||
"lastRomVersion": profile["lastRomVersion"],
|
|
||||||
"lastLoginDate": profile["lastLoginDate"],
|
"lastLoginDate": profile["lastLoginDate"],
|
||||||
"lastPlayDate": profile["lastPlayDate"],
|
"lastPlayDate": profile["lastPlayDate"],
|
||||||
"playerRating": profile["playerRating"],
|
"playerRating": profile["playerRating"],
|
||||||
"nameplateId": 0, # Unused
|
"nameplateId": 0, # Unused
|
||||||
|
"frameId": profile["frameId"],
|
||||||
"iconId": profile["iconId"],
|
"iconId": profile["iconId"],
|
||||||
"trophyId": 0, # Unused
|
"trophyId": 0, # Unused
|
||||||
"partnerId": profile["partnerId"],
|
"partnerId": profile["partnerId"],
|
||||||
"frameId": profile["frameId"],
|
"dispRate": option["dispRate"], # 0: all, 1: dispRate, 2: dispDan, 3: hide
|
||||||
"dispRate": option[
|
"dispRank": 0, # TODO
|
||||||
"dispRate"
|
"dispHomeRanker": 0, # TODO
|
||||||
], # 0: all/begin, 1: disprate, 2: dispDan, 3: hide, 4: end
|
"dispTotalLv": 0, # TODO
|
||||||
"totalAwake": profile["totalAwake"],
|
"totalLv": 0, # TODO
|
||||||
"isNetMember": profile["isNetMember"],
|
|
||||||
"dailyBonusDate": profile["dailyBonusDate"],
|
|
||||||
"headPhoneVolume": option["headPhoneVolume"],
|
|
||||||
"isInherit": False, # Not sure what this is or does??
|
|
||||||
"banState": profile["banState"]
|
|
||||||
if profile["banState"] is not None
|
|
||||||
else 0, # New with uni+
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def handle_user_login_api_request(self, data: Dict) -> Dict:
|
def handle_user_login_api_request(self, data: Dict) -> Dict:
|
||||||
|
@ -163,7 +157,6 @@ class Mai2Base:
|
||||||
"lastLoginDate": lastLoginDate,
|
"lastLoginDate": lastLoginDate,
|
||||||
"loginCount": loginCt,
|
"loginCount": loginCt,
|
||||||
"consecutiveLoginCount": 0, # We don't really have a way to track this...
|
"consecutiveLoginCount": 0, # We don't really have a way to track this...
|
||||||
"loginId": loginCt, # Used with the playlog!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def handle_upload_user_playlog_api_request(self, data: Dict) -> Dict:
|
def handle_upload_user_playlog_api_request(self, data: Dict) -> Dict:
|
||||||
|
|
|
@ -19,7 +19,36 @@ class Mai2ServerConfig:
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Mai2DeliverConfig:
|
||||||
|
def __init__(self, parent: "Mai2Config") -> None:
|
||||||
|
self.__config = parent
|
||||||
|
|
||||||
|
@property
|
||||||
|
def enable(self) -> bool:
|
||||||
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "mai2", "deliver", "enable", default=False
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def udbdl_enable(self) -> bool:
|
||||||
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "mai2", "deliver", "udbdl_enable", default=False
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def list_folder(self) -> int:
|
||||||
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "mai2", "server", "list_folder", default=""
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def list_folder(self) -> int:
|
||||||
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "mai2", "server", "content_folder", default=""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Mai2Config(dict):
|
class Mai2Config(dict):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.server = Mai2ServerConfig(self)
|
self.server = Mai2ServerConfig(self)
|
||||||
|
self.deliver = Mai2DeliverConfig(self)
|
||||||
|
|
|
@ -20,19 +20,53 @@ class Mai2Constants:
|
||||||
|
|
||||||
DATE_TIME_FORMAT = "%Y-%m-%d %H:%M:%S"
|
DATE_TIME_FORMAT = "%Y-%m-%d %H:%M:%S"
|
||||||
|
|
||||||
GAME_CODE = "SDEZ"
|
GAME_CODE = "SBXL"
|
||||||
|
GAME_CODE_GREEN = "SBZF"
|
||||||
|
GAME_CODE_ORANGE = "SDBM"
|
||||||
|
GAME_CODE_PINK = "SDCQ"
|
||||||
|
GAME_CODE_MURASAKI = "SDDK"
|
||||||
|
GAME_CODE_MILK = "SDDZ"
|
||||||
|
GAME_CODE_FINALE = "SDEY"
|
||||||
|
GAME_CODE_DX = "SDEZ"
|
||||||
|
|
||||||
CONFIG_NAME = "mai2.yaml"
|
CONFIG_NAME = "mai2.yaml"
|
||||||
|
|
||||||
VER_MAIMAI_DX = 0
|
VER_MAIMAI = 0
|
||||||
VER_MAIMAI_DX_PLUS = 1
|
VER_MAIMAI_PLUS = 1
|
||||||
VER_MAIMAI_DX_SPLASH = 2
|
VER_MAIMAI_GREEN = 2
|
||||||
VER_MAIMAI_DX_SPLASH_PLUS = 3
|
VER_MAIMAI_GREEN_PLUS = 3
|
||||||
VER_MAIMAI_DX_UNIVERSE = 4
|
VER_MAIMAI_ORANGE = 4
|
||||||
VER_MAIMAI_DX_UNIVERSE_PLUS = 5
|
VER_MAIMAI_ORANGE_PLUS = 5
|
||||||
VER_MAIMAI_DX_FESTIVAL = 6
|
VER_MAIMAI_PINK = 6
|
||||||
|
VER_MAIMAI_PINK_PLUS = 7
|
||||||
|
VER_MAIMAI_MURASAKI = 8
|
||||||
|
VER_MAIMAI_MURASAKI_PLUS = 9
|
||||||
|
VER_MAIMAI_MILK = 10
|
||||||
|
VER_MAIMAI_MILK_PLUS = 11
|
||||||
|
VER_MAIMAI_FINALE = 12
|
||||||
|
|
||||||
|
VER_MAIMAI_DX = 13
|
||||||
|
VER_MAIMAI_DX_PLUS = 14
|
||||||
|
VER_MAIMAI_DX_SPLASH = 15
|
||||||
|
VER_MAIMAI_DX_SPLASH_PLUS = 16
|
||||||
|
VER_MAIMAI_DX_UNIVERSE = 17
|
||||||
|
VER_MAIMAI_DX_UNIVERSE_PLUS = 18
|
||||||
|
VER_MAIMAI_DX_FESTIVAL = 19
|
||||||
|
|
||||||
VERSION_STRING = (
|
VERSION_STRING = (
|
||||||
|
"maimai",
|
||||||
|
"maimai PLUS",
|
||||||
|
"maimai GreeN",
|
||||||
|
"maimai GreeN PLUS",
|
||||||
|
"maimai ORANGE",
|
||||||
|
"maimai ORANGE PLUS",
|
||||||
|
"maimai PiNK",
|
||||||
|
"maimai PiNK PLUS",
|
||||||
|
"maimai MURASAKi",
|
||||||
|
"maimai MURASAKi PLUS",
|
||||||
|
"maimai MiLK",
|
||||||
|
"maimai MiLK PLUS",
|
||||||
|
"maimai FiNALE",
|
||||||
"maimai DX",
|
"maimai DX",
|
||||||
"maimai DX PLUS",
|
"maimai DX PLUS",
|
||||||
"maimai DX Splash",
|
"maimai DX Splash",
|
||||||
|
|
|
@ -0,0 +1,746 @@
|
||||||
|
from typing import Any, List, Dict
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import pytz
|
||||||
|
import json
|
||||||
|
from random import randint
|
||||||
|
|
||||||
|
from core.config import CoreConfig
|
||||||
|
from titles.mai2.base import Mai2Base
|
||||||
|
from titles.mai2.config import Mai2Config
|
||||||
|
from titles.mai2.const import Mai2Constants
|
||||||
|
|
||||||
|
|
||||||
|
class Mai2DX(Mai2Base):
|
||||||
|
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
||||||
|
super().__init__(cfg, game_cfg)
|
||||||
|
self.version = Mai2Constants.VER_MAIMAI_DX
|
||||||
|
|
||||||
|
if self.core_config.server.is_develop and self.core_config.title.port > 0:
|
||||||
|
self.old_server = f"http://{self.core_config.title.hostname}:{self.core_config.title.port}/SDEZ/100/"
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.old_server = f"http://{self.core_config.title.hostname}/SDEZ/100/"
|
||||||
|
|
||||||
|
def handle_get_user_preview_api_request(self, data: Dict) -> Dict:
|
||||||
|
p = self.data.profile.get_profile_detail(data["userId"], self.version)
|
||||||
|
o = self.data.profile.get_profile_option(data["userId"], self.version)
|
||||||
|
if p is None or o is None:
|
||||||
|
return {} # Register
|
||||||
|
profile = p._asdict()
|
||||||
|
option = o._asdict()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"userId": data["userId"],
|
||||||
|
"userName": profile["userName"],
|
||||||
|
"isLogin": False,
|
||||||
|
"lastGameId": profile["lastGameId"],
|
||||||
|
"lastDataVersion": profile["lastDataVersion"],
|
||||||
|
"lastRomVersion": profile["lastRomVersion"],
|
||||||
|
"lastLoginDate": profile["lastLoginDate"],
|
||||||
|
"lastPlayDate": profile["lastPlayDate"],
|
||||||
|
"playerRating": profile["playerRating"],
|
||||||
|
"nameplateId": 0, # Unused
|
||||||
|
"iconId": profile["iconId"],
|
||||||
|
"trophyId": 0, # Unused
|
||||||
|
"partnerId": profile["partnerId"],
|
||||||
|
"frameId": profile["frameId"],
|
||||||
|
"dispRate": option[
|
||||||
|
"dispRate"
|
||||||
|
], # 0: all/begin, 1: disprate, 2: dispDan, 3: hide, 4: end
|
||||||
|
"totalAwake": profile["totalAwake"],
|
||||||
|
"isNetMember": profile["isNetMember"],
|
||||||
|
"dailyBonusDate": profile["dailyBonusDate"],
|
||||||
|
"headPhoneVolume": option["headPhoneVolume"],
|
||||||
|
"isInherit": False, # Not sure what this is or does??
|
||||||
|
"banState": profile["banState"]
|
||||||
|
if profile["banState"] is not None
|
||||||
|
else 0, # New with uni+
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_user_login_api_request(self, data: Dict) -> Dict:
|
||||||
|
profile = self.data.profile.get_profile_detail(data["userId"], self.version)
|
||||||
|
|
||||||
|
if profile is not None:
|
||||||
|
lastLoginDate = profile["lastLoginDate"]
|
||||||
|
loginCt = profile["playCount"]
|
||||||
|
|
||||||
|
if "regionId" in data:
|
||||||
|
self.data.profile.put_profile_region(data["userId"], data["regionId"])
|
||||||
|
else:
|
||||||
|
loginCt = 0
|
||||||
|
lastLoginDate = "2017-12-05 07:00:00.0"
|
||||||
|
|
||||||
|
return {
|
||||||
|
"returnCode": 1,
|
||||||
|
"lastLoginDate": lastLoginDate,
|
||||||
|
"loginCount": loginCt,
|
||||||
|
"consecutiveLoginCount": 0, # We don't really have a way to track this...
|
||||||
|
"loginId": loginCt, # Used with the playlog!
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_upload_user_playlog_api_request(self, data: Dict) -> Dict:
|
||||||
|
user_id = data["userId"]
|
||||||
|
playlog = data["userPlaylog"]
|
||||||
|
|
||||||
|
self.data.score.put_playlog(user_id, playlog)
|
||||||
|
|
||||||
|
return {"returnCode": 1, "apiName": "UploadUserPlaylogApi"}
|
||||||
|
|
||||||
|
def handle_upsert_user_chargelog_api_request(self, data: Dict) -> Dict:
|
||||||
|
user_id = data["userId"]
|
||||||
|
charge = data["userCharge"]
|
||||||
|
|
||||||
|
# remove the ".0" from the date string, festival only?
|
||||||
|
charge["purchaseDate"] = charge["purchaseDate"].replace(".0", "")
|
||||||
|
self.data.item.put_charge(
|
||||||
|
user_id,
|
||||||
|
charge["chargeId"],
|
||||||
|
charge["stock"],
|
||||||
|
datetime.strptime(charge["purchaseDate"], Mai2Constants.DATE_TIME_FORMAT),
|
||||||
|
datetime.strptime(charge["validDate"], Mai2Constants.DATE_TIME_FORMAT),
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"returnCode": 1, "apiName": "UpsertUserChargelogApi"}
|
||||||
|
|
||||||
|
def handle_upsert_user_all_api_request(self, data: Dict) -> Dict:
|
||||||
|
user_id = data["userId"]
|
||||||
|
upsert = data["upsertUserAll"]
|
||||||
|
|
||||||
|
if "userData" in upsert and len(upsert["userData"]) > 0:
|
||||||
|
upsert["userData"][0]["isNetMember"] = 1
|
||||||
|
upsert["userData"][0].pop("accessCode")
|
||||||
|
self.data.profile.put_profile_detail(
|
||||||
|
user_id, self.version, upsert["userData"][0]
|
||||||
|
)
|
||||||
|
|
||||||
|
if "userExtend" in upsert and len(upsert["userExtend"]) > 0:
|
||||||
|
self.data.profile.put_profile_extend(
|
||||||
|
user_id, self.version, upsert["userExtend"][0]
|
||||||
|
)
|
||||||
|
|
||||||
|
if "userGhost" in upsert:
|
||||||
|
for ghost in upsert["userGhost"]:
|
||||||
|
self.data.profile.put_profile_extend(user_id, self.version, ghost)
|
||||||
|
|
||||||
|
if "userOption" in upsert and len(upsert["userOption"]) > 0:
|
||||||
|
self.data.profile.put_profile_option(
|
||||||
|
user_id, self.version, upsert["userOption"][0]
|
||||||
|
)
|
||||||
|
|
||||||
|
if "userRatingList" in upsert and len(upsert["userRatingList"]) > 0:
|
||||||
|
self.data.profile.put_profile_rating(
|
||||||
|
user_id, self.version, upsert["userRatingList"][0]
|
||||||
|
)
|
||||||
|
|
||||||
|
if "userActivityList" in upsert and len(upsert["userActivityList"]) > 0:
|
||||||
|
for k, v in upsert["userActivityList"][0].items():
|
||||||
|
for act in v:
|
||||||
|
self.data.profile.put_profile_activity(user_id, act)
|
||||||
|
|
||||||
|
if "userChargeList" in upsert and len(upsert["userChargeList"]) > 0:
|
||||||
|
for charge in upsert["userChargeList"]:
|
||||||
|
# remove the ".0" from the date string, festival only?
|
||||||
|
charge["purchaseDate"] = charge["purchaseDate"].replace(".0", "")
|
||||||
|
self.data.item.put_charge(
|
||||||
|
user_id,
|
||||||
|
charge["chargeId"],
|
||||||
|
charge["stock"],
|
||||||
|
datetime.strptime(
|
||||||
|
charge["purchaseDate"], Mai2Constants.DATE_TIME_FORMAT
|
||||||
|
),
|
||||||
|
datetime.strptime(
|
||||||
|
charge["validDate"], Mai2Constants.DATE_TIME_FORMAT
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
if "userCharacterList" in upsert and len(upsert["userCharacterList"]) > 0:
|
||||||
|
for char in upsert["userCharacterList"]:
|
||||||
|
self.data.item.put_character(
|
||||||
|
user_id,
|
||||||
|
char["characterId"],
|
||||||
|
char["level"],
|
||||||
|
char["awakening"],
|
||||||
|
char["useCount"],
|
||||||
|
)
|
||||||
|
|
||||||
|
if "userItemList" in upsert and len(upsert["userItemList"]) > 0:
|
||||||
|
for item in upsert["userItemList"]:
|
||||||
|
self.data.item.put_item(
|
||||||
|
user_id,
|
||||||
|
int(item["itemKind"]),
|
||||||
|
item["itemId"],
|
||||||
|
item["stock"],
|
||||||
|
item["isValid"],
|
||||||
|
)
|
||||||
|
|
||||||
|
if "userLoginBonusList" in upsert and len(upsert["userLoginBonusList"]) > 0:
|
||||||
|
for login_bonus in upsert["userLoginBonusList"]:
|
||||||
|
self.data.item.put_login_bonus(
|
||||||
|
user_id,
|
||||||
|
login_bonus["bonusId"],
|
||||||
|
login_bonus["point"],
|
||||||
|
login_bonus["isCurrent"],
|
||||||
|
login_bonus["isComplete"],
|
||||||
|
)
|
||||||
|
|
||||||
|
if "userMapList" in upsert and len(upsert["userMapList"]) > 0:
|
||||||
|
for map in upsert["userMapList"]:
|
||||||
|
self.data.item.put_map(
|
||||||
|
user_id,
|
||||||
|
map["mapId"],
|
||||||
|
map["distance"],
|
||||||
|
map["isLock"],
|
||||||
|
map["isClear"],
|
||||||
|
map["isComplete"],
|
||||||
|
)
|
||||||
|
|
||||||
|
if "userMusicDetailList" in upsert and len(upsert["userMusicDetailList"]) > 0:
|
||||||
|
for music in upsert["userMusicDetailList"]:
|
||||||
|
self.data.score.put_best_score(user_id, music)
|
||||||
|
|
||||||
|
if "userCourseList" in upsert and len(upsert["userCourseList"]) > 0:
|
||||||
|
for course in upsert["userCourseList"]:
|
||||||
|
self.data.score.put_course(user_id, course)
|
||||||
|
|
||||||
|
if "userFavoriteList" in upsert and len(upsert["userFavoriteList"]) > 0:
|
||||||
|
for fav in upsert["userFavoriteList"]:
|
||||||
|
self.data.item.put_favorite(user_id, fav["kind"], fav["itemIdList"])
|
||||||
|
|
||||||
|
if (
|
||||||
|
"userFriendSeasonRankingList" in upsert
|
||||||
|
and len(upsert["userFriendSeasonRankingList"]) > 0
|
||||||
|
):
|
||||||
|
for fsr in upsert["userFriendSeasonRankingList"]:
|
||||||
|
fsr["recordDate"] = (
|
||||||
|
datetime.strptime(
|
||||||
|
fsr["recordDate"], f"{Mai2Constants.DATE_TIME_FORMAT}.0"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.data.item.put_friend_season_ranking(user_id, fsr)
|
||||||
|
|
||||||
|
return {"returnCode": 1, "apiName": "UpsertUserAllApi"}
|
||||||
|
|
||||||
|
def handle_user_logout_api_request(self, data: Dict) -> Dict:
|
||||||
|
return {"returnCode": 1}
|
||||||
|
|
||||||
|
def handle_get_user_data_api_request(self, data: Dict) -> Dict:
|
||||||
|
profile = self.data.profile.get_profile_detail(data["userId"], self.version)
|
||||||
|
if profile is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
profile_dict = profile._asdict()
|
||||||
|
profile_dict.pop("id")
|
||||||
|
profile_dict.pop("user")
|
||||||
|
profile_dict.pop("version")
|
||||||
|
|
||||||
|
return {"userId": data["userId"], "userData": profile_dict}
|
||||||
|
|
||||||
|
def handle_get_user_extend_api_request(self, data: Dict) -> Dict:
|
||||||
|
extend = self.data.profile.get_profile_extend(data["userId"], self.version)
|
||||||
|
if extend is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
extend_dict = extend._asdict()
|
||||||
|
extend_dict.pop("id")
|
||||||
|
extend_dict.pop("user")
|
||||||
|
extend_dict.pop("version")
|
||||||
|
|
||||||
|
return {"userId": data["userId"], "userExtend": extend_dict}
|
||||||
|
|
||||||
|
def handle_get_user_option_api_request(self, data: Dict) -> Dict:
|
||||||
|
options = self.data.profile.get_profile_option(data["userId"], self.version)
|
||||||
|
if options is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
options_dict = options._asdict()
|
||||||
|
options_dict.pop("id")
|
||||||
|
options_dict.pop("user")
|
||||||
|
options_dict.pop("version")
|
||||||
|
|
||||||
|
return {"userId": data["userId"], "userOption": options_dict}
|
||||||
|
|
||||||
|
def handle_get_user_card_api_request(self, data: Dict) -> Dict:
|
||||||
|
user_cards = self.data.item.get_cards(data["userId"])
|
||||||
|
if user_cards is None:
|
||||||
|
return {"userId": data["userId"], "nextIndex": 0, "userCardList": []}
|
||||||
|
|
||||||
|
max_ct = data["maxCount"]
|
||||||
|
next_idx = data["nextIndex"]
|
||||||
|
start_idx = next_idx
|
||||||
|
end_idx = max_ct + start_idx
|
||||||
|
|
||||||
|
if len(user_cards[start_idx:]) > max_ct:
|
||||||
|
next_idx += max_ct
|
||||||
|
else:
|
||||||
|
next_idx = 0
|
||||||
|
|
||||||
|
card_list = []
|
||||||
|
for card in user_cards:
|
||||||
|
tmp = card._asdict()
|
||||||
|
tmp.pop("id")
|
||||||
|
tmp.pop("user")
|
||||||
|
tmp["startDate"] = datetime.strftime(
|
||||||
|
tmp["startDate"], Mai2Constants.DATE_TIME_FORMAT
|
||||||
|
)
|
||||||
|
tmp["endDate"] = datetime.strftime(
|
||||||
|
tmp["endDate"], Mai2Constants.DATE_TIME_FORMAT
|
||||||
|
)
|
||||||
|
card_list.append(tmp)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"userId": data["userId"],
|
||||||
|
"nextIndex": next_idx,
|
||||||
|
"userCardList": card_list[start_idx:end_idx],
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_get_user_charge_api_request(self, data: Dict) -> Dict:
|
||||||
|
user_charges = self.data.item.get_charges(data["userId"])
|
||||||
|
if user_charges is None:
|
||||||
|
return {"userId": data["userId"], "length": 0, "userChargeList": []}
|
||||||
|
|
||||||
|
user_charge_list = []
|
||||||
|
for charge in user_charges:
|
||||||
|
tmp = charge._asdict()
|
||||||
|
tmp.pop("id")
|
||||||
|
tmp.pop("user")
|
||||||
|
tmp["purchaseDate"] = datetime.strftime(
|
||||||
|
tmp["purchaseDate"], Mai2Constants.DATE_TIME_FORMAT
|
||||||
|
)
|
||||||
|
tmp["validDate"] = datetime.strftime(
|
||||||
|
tmp["validDate"], Mai2Constants.DATE_TIME_FORMAT
|
||||||
|
)
|
||||||
|
|
||||||
|
user_charge_list.append(tmp)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"userId": data["userId"],
|
||||||
|
"length": len(user_charge_list),
|
||||||
|
"userChargeList": user_charge_list,
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_get_user_item_api_request(self, data: Dict) -> Dict:
|
||||||
|
kind = int(data["nextIndex"] / 10000000000)
|
||||||
|
next_idx = int(data["nextIndex"] % 10000000000)
|
||||||
|
user_item_list = self.data.item.get_items(data["userId"], kind)
|
||||||
|
|
||||||
|
items: list[Dict[str, Any]] = []
|
||||||
|
for i in range(next_idx, len(user_item_list)):
|
||||||
|
tmp = user_item_list[i]._asdict()
|
||||||
|
tmp.pop("user")
|
||||||
|
tmp.pop("id")
|
||||||
|
items.append(tmp)
|
||||||
|
if len(items) >= int(data["maxCount"]):
|
||||||
|
break
|
||||||
|
|
||||||
|
xout = kind * 10000000000 + next_idx + len(items)
|
||||||
|
|
||||||
|
if len(items) < int(data["maxCount"]):
|
||||||
|
next_idx = 0
|
||||||
|
else:
|
||||||
|
next_idx = xout
|
||||||
|
|
||||||
|
return {
|
||||||
|
"userId": data["userId"],
|
||||||
|
"nextIndex": next_idx,
|
||||||
|
"itemKind": kind,
|
||||||
|
"userItemList": items,
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_get_user_character_api_request(self, data: Dict) -> Dict:
|
||||||
|
characters = self.data.item.get_characters(data["userId"])
|
||||||
|
|
||||||
|
chara_list = []
|
||||||
|
for chara in characters:
|
||||||
|
tmp = chara._asdict()
|
||||||
|
tmp.pop("id")
|
||||||
|
tmp.pop("user")
|
||||||
|
chara_list.append(tmp)
|
||||||
|
|
||||||
|
return {"userId": data["userId"], "userCharacterList": chara_list}
|
||||||
|
|
||||||
|
def handle_get_user_favorite_api_request(self, data: Dict) -> Dict:
|
||||||
|
favorites = self.data.item.get_favorites(data["userId"], data["itemKind"])
|
||||||
|
if favorites is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
userFavs = []
|
||||||
|
for fav in favorites:
|
||||||
|
userFavs.append(
|
||||||
|
{
|
||||||
|
"userId": data["userId"],
|
||||||
|
"itemKind": fav["itemKind"],
|
||||||
|
"itemIdList": fav["itemIdList"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"userId": data["userId"], "userFavoriteData": userFavs}
|
||||||
|
|
||||||
|
def handle_get_user_ghost_api_request(self, data: Dict) -> Dict:
|
||||||
|
ghost = self.data.profile.get_profile_ghost(data["userId"], self.version)
|
||||||
|
if ghost is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
ghost_dict = ghost._asdict()
|
||||||
|
ghost_dict.pop("user")
|
||||||
|
ghost_dict.pop("id")
|
||||||
|
ghost_dict.pop("version_int")
|
||||||
|
|
||||||
|
return {"userId": data["userId"], "userGhost": ghost_dict}
|
||||||
|
|
||||||
|
def handle_get_user_rating_api_request(self, data: Dict) -> Dict:
|
||||||
|
rating = self.data.profile.get_profile_rating(data["userId"], self.version)
|
||||||
|
if rating is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
rating_dict = rating._asdict()
|
||||||
|
rating_dict.pop("user")
|
||||||
|
rating_dict.pop("id")
|
||||||
|
rating_dict.pop("version")
|
||||||
|
|
||||||
|
return {"userId": data["userId"], "userRating": rating_dict}
|
||||||
|
|
||||||
|
def handle_get_user_activity_api_request(self, data: Dict) -> Dict:
|
||||||
|
"""
|
||||||
|
kind 1 is playlist, kind 2 is music list
|
||||||
|
"""
|
||||||
|
playlist = self.data.profile.get_profile_activity(data["userId"], 1)
|
||||||
|
musiclist = self.data.profile.get_profile_activity(data["userId"], 2)
|
||||||
|
if playlist is None or musiclist is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
plst = []
|
||||||
|
mlst = []
|
||||||
|
|
||||||
|
for play in playlist:
|
||||||
|
tmp = play._asdict()
|
||||||
|
tmp["id"] = tmp["activityId"]
|
||||||
|
tmp.pop("activityId")
|
||||||
|
tmp.pop("user")
|
||||||
|
plst.append(tmp)
|
||||||
|
|
||||||
|
for music in musiclist:
|
||||||
|
tmp = music._asdict()
|
||||||
|
tmp["id"] = tmp["activityId"]
|
||||||
|
tmp.pop("activityId")
|
||||||
|
tmp.pop("user")
|
||||||
|
mlst.append(tmp)
|
||||||
|
|
||||||
|
return {"userActivity": {"playList": plst, "musicList": mlst}}
|
||||||
|
|
||||||
|
def handle_get_user_course_api_request(self, data: Dict) -> Dict:
|
||||||
|
user_courses = self.data.score.get_courses(data["userId"])
|
||||||
|
if user_courses is None:
|
||||||
|
return {"userId": data["userId"], "nextIndex": 0, "userCourseList": []}
|
||||||
|
|
||||||
|
course_list = []
|
||||||
|
for course in user_courses:
|
||||||
|
tmp = course._asdict()
|
||||||
|
tmp.pop("user")
|
||||||
|
tmp.pop("id")
|
||||||
|
course_list.append(tmp)
|
||||||
|
|
||||||
|
return {"userId": data["userId"], "nextIndex": 0, "userCourseList": course_list}
|
||||||
|
|
||||||
|
def handle_get_user_portrait_api_request(self, data: Dict) -> Dict:
|
||||||
|
# No support for custom pfps
|
||||||
|
return {"length": 0, "userPortraitList": []}
|
||||||
|
|
||||||
|
def handle_get_user_friend_season_ranking_api_request(self, data: Dict) -> Dict:
|
||||||
|
friend_season_ranking = self.data.item.get_friend_season_ranking(data["userId"])
|
||||||
|
if friend_season_ranking is None:
|
||||||
|
return {
|
||||||
|
"userId": data["userId"],
|
||||||
|
"nextIndex": 0,
|
||||||
|
"userFriendSeasonRankingList": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
friend_season_ranking_list = []
|
||||||
|
next_idx = int(data["nextIndex"])
|
||||||
|
max_ct = int(data["maxCount"])
|
||||||
|
|
||||||
|
for x in range(next_idx, len(friend_season_ranking)):
|
||||||
|
tmp = friend_season_ranking[x]._asdict()
|
||||||
|
tmp.pop("user")
|
||||||
|
tmp.pop("id")
|
||||||
|
tmp["recordDate"] = datetime.strftime(
|
||||||
|
tmp["recordDate"], f"{Mai2Constants.DATE_TIME_FORMAT}.0"
|
||||||
|
)
|
||||||
|
friend_season_ranking_list.append(tmp)
|
||||||
|
|
||||||
|
if len(friend_season_ranking_list) >= max_ct:
|
||||||
|
break
|
||||||
|
|
||||||
|
if len(friend_season_ranking) >= next_idx + max_ct:
|
||||||
|
next_idx += max_ct
|
||||||
|
else:
|
||||||
|
next_idx = 0
|
||||||
|
|
||||||
|
return {
|
||||||
|
"userId": data["userId"],
|
||||||
|
"nextIndex": next_idx,
|
||||||
|
"userFriendSeasonRankingList": friend_season_ranking_list,
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_get_user_map_api_request(self, data: Dict) -> Dict:
|
||||||
|
maps = self.data.item.get_maps(data["userId"])
|
||||||
|
if maps is None:
|
||||||
|
return {
|
||||||
|
"userId": data["userId"],
|
||||||
|
"nextIndex": 0,
|
||||||
|
"userMapList": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
map_list = []
|
||||||
|
next_idx = int(data["nextIndex"])
|
||||||
|
max_ct = int(data["maxCount"])
|
||||||
|
|
||||||
|
for x in range(next_idx, len(maps)):
|
||||||
|
tmp = maps[x]._asdict()
|
||||||
|
tmp.pop("user")
|
||||||
|
tmp.pop("id")
|
||||||
|
map_list.append(tmp)
|
||||||
|
|
||||||
|
if len(map_list) >= max_ct:
|
||||||
|
break
|
||||||
|
|
||||||
|
if len(maps) >= next_idx + max_ct:
|
||||||
|
next_idx += max_ct
|
||||||
|
else:
|
||||||
|
next_idx = 0
|
||||||
|
|
||||||
|
return {
|
||||||
|
"userId": data["userId"],
|
||||||
|
"nextIndex": next_idx,
|
||||||
|
"userMapList": map_list,
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_get_user_login_bonus_api_request(self, data: Dict) -> Dict:
|
||||||
|
login_bonuses = self.data.item.get_login_bonuses(data["userId"])
|
||||||
|
if login_bonuses is None:
|
||||||
|
return {
|
||||||
|
"userId": data["userId"],
|
||||||
|
"nextIndex": 0,
|
||||||
|
"userLoginBonusList": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
login_bonus_list = []
|
||||||
|
next_idx = int(data["nextIndex"])
|
||||||
|
max_ct = int(data["maxCount"])
|
||||||
|
|
||||||
|
for x in range(next_idx, len(login_bonuses)):
|
||||||
|
tmp = login_bonuses[x]._asdict()
|
||||||
|
tmp.pop("user")
|
||||||
|
tmp.pop("id")
|
||||||
|
login_bonus_list.append(tmp)
|
||||||
|
|
||||||
|
if len(login_bonus_list) >= max_ct:
|
||||||
|
break
|
||||||
|
|
||||||
|
if len(login_bonuses) >= next_idx + max_ct:
|
||||||
|
next_idx += max_ct
|
||||||
|
else:
|
||||||
|
next_idx = 0
|
||||||
|
|
||||||
|
return {
|
||||||
|
"userId": data["userId"],
|
||||||
|
"nextIndex": next_idx,
|
||||||
|
"userLoginBonusList": login_bonus_list,
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_get_user_region_api_request(self, data: Dict) -> Dict:
|
||||||
|
return {"userId": data["userId"], "length": 0, "userRegionList": []}
|
||||||
|
|
||||||
|
def handle_get_user_music_api_request(self, data: Dict) -> Dict:
|
||||||
|
songs = self.data.score.get_best_scores(data["userId"])
|
||||||
|
music_detail_list = []
|
||||||
|
next_index = 0
|
||||||
|
|
||||||
|
if songs is not None:
|
||||||
|
for song in songs:
|
||||||
|
tmp = song._asdict()
|
||||||
|
tmp.pop("id")
|
||||||
|
tmp.pop("user")
|
||||||
|
music_detail_list.append(tmp)
|
||||||
|
|
||||||
|
if len(music_detail_list) == data["maxCount"]:
|
||||||
|
next_index = data["maxCount"] + data["nextIndex"]
|
||||||
|
break
|
||||||
|
|
||||||
|
return {
|
||||||
|
"userId": data["userId"],
|
||||||
|
"nextIndex": next_index,
|
||||||
|
"userMusicList": [{"userMusicDetailList": music_detail_list}],
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
|
||||||
|
p = self.data.profile.get_profile_detail(data["userId"], self.version)
|
||||||
|
if p is None:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"userName": p["userName"],
|
||||||
|
"rating": p["playerRating"],
|
||||||
|
# hardcode lastDataVersion for CardMaker 1.34
|
||||||
|
"lastDataVersion": "1.20.00",
|
||||||
|
"isLogin": False,
|
||||||
|
"isExistSellingCard": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_cm_get_user_data_api_request(self, data: Dict) -> Dict:
|
||||||
|
# user already exists, because the preview checks that already
|
||||||
|
p = self.data.profile.get_profile_detail(data["userId"], self.version)
|
||||||
|
|
||||||
|
cards = self.data.card.get_user_cards(data["userId"])
|
||||||
|
if cards is None or len(cards) == 0:
|
||||||
|
# This should never happen
|
||||||
|
self.logger.error(
|
||||||
|
f"handle_get_user_data_api_request: Internal error - No cards found for user id {data['userId']}"
|
||||||
|
)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# get the dict representation of the row so we can modify values
|
||||||
|
user_data = p._asdict()
|
||||||
|
|
||||||
|
# remove the values the game doesn't want
|
||||||
|
user_data.pop("id")
|
||||||
|
user_data.pop("user")
|
||||||
|
user_data.pop("version")
|
||||||
|
|
||||||
|
return {"userId": data["userId"], "userData": user_data}
|
||||||
|
|
||||||
|
def handle_cm_login_api_request(self, data: Dict) -> Dict:
|
||||||
|
return {"returnCode": 1}
|
||||||
|
|
||||||
|
def handle_cm_logout_api_request(self, data: Dict) -> Dict:
|
||||||
|
return {"returnCode": 1}
|
||||||
|
|
||||||
|
def handle_cm_get_selling_card_api_request(self, data: Dict) -> Dict:
|
||||||
|
selling_cards = self.data.static.get_enabled_cards(self.version)
|
||||||
|
if selling_cards is None:
|
||||||
|
return {"length": 0, "sellingCardList": []}
|
||||||
|
|
||||||
|
selling_card_list = []
|
||||||
|
for card in selling_cards:
|
||||||
|
tmp = card._asdict()
|
||||||
|
tmp.pop("id")
|
||||||
|
tmp.pop("version")
|
||||||
|
tmp.pop("cardName")
|
||||||
|
tmp.pop("enabled")
|
||||||
|
|
||||||
|
tmp["startDate"] = datetime.strftime(tmp["startDate"], "%Y-%m-%d %H:%M:%S")
|
||||||
|
tmp["endDate"] = datetime.strftime(tmp["endDate"], "%Y-%m-%d %H:%M:%S")
|
||||||
|
tmp["noticeStartDate"] = datetime.strftime(
|
||||||
|
tmp["noticeStartDate"], "%Y-%m-%d %H:%M:%S"
|
||||||
|
)
|
||||||
|
tmp["noticeEndDate"] = datetime.strftime(
|
||||||
|
tmp["noticeEndDate"], "%Y-%m-%d %H:%M:%S"
|
||||||
|
)
|
||||||
|
|
||||||
|
selling_card_list.append(tmp)
|
||||||
|
|
||||||
|
return {"length": len(selling_card_list), "sellingCardList": selling_card_list}
|
||||||
|
|
||||||
|
def handle_cm_get_user_card_api_request(self, data: Dict) -> Dict:
|
||||||
|
user_cards = self.data.item.get_cards(data["userId"])
|
||||||
|
if user_cards is None:
|
||||||
|
return {"returnCode": 1, "length": 0, "nextIndex": 0, "userCardList": []}
|
||||||
|
|
||||||
|
max_ct = data["maxCount"]
|
||||||
|
next_idx = data["nextIndex"]
|
||||||
|
start_idx = next_idx
|
||||||
|
end_idx = max_ct + start_idx
|
||||||
|
|
||||||
|
if len(user_cards[start_idx:]) > max_ct:
|
||||||
|
next_idx += max_ct
|
||||||
|
else:
|
||||||
|
next_idx = 0
|
||||||
|
|
||||||
|
card_list = []
|
||||||
|
for card in user_cards:
|
||||||
|
tmp = card._asdict()
|
||||||
|
tmp.pop("id")
|
||||||
|
tmp.pop("user")
|
||||||
|
|
||||||
|
tmp["startDate"] = datetime.strftime(tmp["startDate"], "%Y-%m-%d %H:%M:%S")
|
||||||
|
tmp["endDate"] = datetime.strftime(tmp["endDate"], "%Y-%m-%d %H:%M:%S")
|
||||||
|
card_list.append(tmp)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"returnCode": 1,
|
||||||
|
"length": len(card_list[start_idx:end_idx]),
|
||||||
|
"nextIndex": next_idx,
|
||||||
|
"userCardList": card_list[start_idx:end_idx],
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_cm_get_user_item_api_request(self, data: Dict) -> Dict:
|
||||||
|
super().handle_get_user_item_api_request(data)
|
||||||
|
|
||||||
|
def handle_cm_get_user_character_api_request(self, data: Dict) -> Dict:
|
||||||
|
characters = self.data.item.get_characters(data["userId"])
|
||||||
|
|
||||||
|
chara_list = []
|
||||||
|
for chara in characters:
|
||||||
|
chara_list.append(
|
||||||
|
{
|
||||||
|
"characterId": chara["characterId"],
|
||||||
|
# no clue why those values are even needed
|
||||||
|
"point": 0,
|
||||||
|
"count": 0,
|
||||||
|
"level": chara["level"],
|
||||||
|
"nextAwake": 0,
|
||||||
|
"nextAwakePercent": 0,
|
||||||
|
"favorite": False,
|
||||||
|
"awakening": chara["awakening"],
|
||||||
|
"useCount": chara["useCount"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"returnCode": 1,
|
||||||
|
"length": len(chara_list),
|
||||||
|
"userCharacterList": chara_list,
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_cm_get_user_card_print_error_api_request(self, data: Dict) -> Dict:
|
||||||
|
return {"length": 0, "userPrintDetailList": []}
|
||||||
|
|
||||||
|
def handle_cm_upsert_user_print_api_request(self, data: Dict) -> Dict:
|
||||||
|
user_id = data["userId"]
|
||||||
|
upsert = data["userPrintDetail"]
|
||||||
|
|
||||||
|
# set a random card serial number
|
||||||
|
serial_id = "".join([str(randint(0, 9)) for _ in range(20)])
|
||||||
|
|
||||||
|
user_card = upsert["userCard"]
|
||||||
|
self.data.item.put_card(
|
||||||
|
user_id,
|
||||||
|
user_card["cardId"],
|
||||||
|
user_card["cardTypeId"],
|
||||||
|
user_card["charaId"],
|
||||||
|
user_card["mapId"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# properly format userPrintDetail for the database
|
||||||
|
upsert.pop("userCard")
|
||||||
|
upsert.pop("serialId")
|
||||||
|
upsert["printDate"] = datetime.strptime(upsert["printDate"], "%Y-%m-%d")
|
||||||
|
|
||||||
|
self.data.item.put_user_print_detail(user_id, serial_id, upsert)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"returnCode": 1,
|
||||||
|
"orderId": 0,
|
||||||
|
"serialId": serial_id,
|
||||||
|
"startDate": "2018-01-01 00:00:00",
|
||||||
|
"endDate": "2038-01-01 00:00:00",
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_cm_upsert_user_printlog_api_request(self, data: Dict) -> Dict:
|
||||||
|
return {
|
||||||
|
"returnCode": 1,
|
||||||
|
"orderId": 0,
|
||||||
|
"serialId": data["userPrintlog"]["serialId"],
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_cm_upsert_buy_card_api_request(self, data: Dict) -> Dict:
|
||||||
|
return {"returnCode": 1}
|
|
@ -4,12 +4,12 @@ import pytz
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from core.config import CoreConfig
|
from core.config import CoreConfig
|
||||||
from titles.mai2.base import Mai2Base
|
from titles.mai2.dx import Mai2DX
|
||||||
from titles.mai2.config import Mai2Config
|
from titles.mai2.config import Mai2Config
|
||||||
from titles.mai2.const import Mai2Constants
|
from titles.mai2.const import Mai2Constants
|
||||||
|
|
||||||
|
|
||||||
class Mai2Plus(Mai2Base):
|
class Mai2DXPlus(Mai2DX):
|
||||||
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
||||||
super().__init__(cfg, game_cfg)
|
super().__init__(cfg, game_cfg)
|
||||||
self.version = Mai2Constants.VER_MAIMAI_DX_PLUS
|
self.version = Mai2Constants.VER_MAIMAI_DX_PLUS
|
|
@ -1,12 +1,12 @@
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from core.config import CoreConfig
|
from core.config import CoreConfig
|
||||||
from titles.mai2.universeplus import Mai2UniversePlus
|
from titles.mai2.dx import Mai2DX
|
||||||
from titles.mai2.const import Mai2Constants
|
from titles.mai2.const import Mai2Constants
|
||||||
from titles.mai2.config import Mai2Config
|
from titles.mai2.config import Mai2Config
|
||||||
|
|
||||||
|
|
||||||
class Mai2Festival(Mai2UniversePlus):
|
class Mai2Festival(Mai2DX):
|
||||||
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
||||||
super().__init__(cfg, game_cfg)
|
super().__init__(cfg, game_cfg)
|
||||||
self.version = Mai2Constants.VER_MAIMAI_DX_FESTIVAL
|
self.version = Mai2Constants.VER_MAIMAI_DX_FESTIVAL
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
from typing import Any, List, Dict
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import pytz
|
||||||
|
import json
|
||||||
|
|
||||||
|
from core.config import CoreConfig
|
||||||
|
from titles.mai2.base import Mai2Base
|
||||||
|
from titles.mai2.config import Mai2Config
|
||||||
|
from titles.mai2.const import Mai2Constants
|
||||||
|
|
||||||
|
|
||||||
|
class Mai2Finale(Mai2Base):
|
||||||
|
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
||||||
|
super().__init__(cfg, game_cfg)
|
||||||
|
self.version = Mai2Constants.VER_MAIMAI_FINALE
|
||||||
|
self.can_deliver = True
|
||||||
|
self.can_usbdl = True
|
||||||
|
|
||||||
|
if self.core_config.server.is_develop and self.core_config.title.port > 0:
|
||||||
|
self.old_server = f"http://{self.core_config.title.hostname}:{self.core_config.title.port}/SDEY/197/"
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.old_server = f"http://{self.core_config.title.hostname}/SDEY/197/"
|
|
@ -14,7 +14,9 @@ from core.utils import Utils
|
||||||
from titles.mai2.config import Mai2Config
|
from titles.mai2.config import Mai2Config
|
||||||
from titles.mai2.const import Mai2Constants
|
from titles.mai2.const import Mai2Constants
|
||||||
from titles.mai2.base import Mai2Base
|
from titles.mai2.base import Mai2Base
|
||||||
from titles.mai2.plus import Mai2Plus
|
from titles.mai2.finale import Mai2Finale
|
||||||
|
from titles.mai2.dx import Mai2DX
|
||||||
|
from titles.mai2.dxplus import Mai2DXPlus
|
||||||
from titles.mai2.splash import Mai2Splash
|
from titles.mai2.splash import Mai2Splash
|
||||||
from titles.mai2.splashplus import Mai2SplashPlus
|
from titles.mai2.splashplus import Mai2SplashPlus
|
||||||
from titles.mai2.universe import Mai2Universe
|
from titles.mai2.universe import Mai2Universe
|
||||||
|
@ -33,7 +35,20 @@ class Mai2Servlet:
|
||||||
|
|
||||||
self.versions = [
|
self.versions = [
|
||||||
Mai2Base,
|
Mai2Base,
|
||||||
Mai2Plus,
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Mai2Finale,
|
||||||
|
Mai2DX,
|
||||||
|
Mai2DXPlus,
|
||||||
Mai2Splash,
|
Mai2Splash,
|
||||||
Mai2SplashPlus,
|
Mai2SplashPlus,
|
||||||
Mai2Universe,
|
Mai2Universe,
|
||||||
|
@ -42,27 +57,29 @@ class Mai2Servlet:
|
||||||
]
|
]
|
||||||
|
|
||||||
self.logger = logging.getLogger("mai2")
|
self.logger = logging.getLogger("mai2")
|
||||||
log_fmt_str = "[%(asctime)s] Mai2 | %(levelname)s | %(message)s"
|
if not hasattr(self.logger, "initted"):
|
||||||
log_fmt = logging.Formatter(log_fmt_str)
|
log_fmt_str = "[%(asctime)s] Mai2 | %(levelname)s | %(message)s"
|
||||||
fileHandler = TimedRotatingFileHandler(
|
log_fmt = logging.Formatter(log_fmt_str)
|
||||||
"{0}/{1}.log".format(self.core_cfg.server.log_dir, "mai2"),
|
fileHandler = TimedRotatingFileHandler(
|
||||||
encoding="utf8",
|
"{0}/{1}.log".format(self.core_cfg.server.log_dir, "mai2"),
|
||||||
when="d",
|
encoding="utf8",
|
||||||
backupCount=10,
|
when="d",
|
||||||
)
|
backupCount=10,
|
||||||
|
)
|
||||||
|
|
||||||
fileHandler.setFormatter(log_fmt)
|
fileHandler.setFormatter(log_fmt)
|
||||||
|
|
||||||
consoleHandler = logging.StreamHandler()
|
consoleHandler = logging.StreamHandler()
|
||||||
consoleHandler.setFormatter(log_fmt)
|
consoleHandler.setFormatter(log_fmt)
|
||||||
|
|
||||||
self.logger.addHandler(fileHandler)
|
self.logger.addHandler(fileHandler)
|
||||||
self.logger.addHandler(consoleHandler)
|
self.logger.addHandler(consoleHandler)
|
||||||
|
|
||||||
self.logger.setLevel(self.game_cfg.server.loglevel)
|
self.logger.setLevel(self.game_cfg.server.loglevel)
|
||||||
coloredlogs.install(
|
coloredlogs.install(
|
||||||
level=self.game_cfg.server.loglevel, logger=self.logger, fmt=log_fmt_str
|
level=self.game_cfg.server.loglevel, logger=self.logger, fmt=log_fmt_str
|
||||||
)
|
)
|
||||||
|
self.logger.initted = True
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_allnet_info(
|
def get_allnet_info(
|
||||||
|
@ -82,13 +99,13 @@ class Mai2Servlet:
|
||||||
return (
|
return (
|
||||||
True,
|
True,
|
||||||
f"http://{core_cfg.title.hostname}:{core_cfg.title.port}/{game_code}/$v/",
|
f"http://{core_cfg.title.hostname}:{core_cfg.title.port}/{game_code}/$v/",
|
||||||
f"{core_cfg.title.hostname}:{core_cfg.title.port}/",
|
f"{core_cfg.title.hostname}",
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
True,
|
True,
|
||||||
f"http://{core_cfg.title.hostname}/{game_code}/$v/",
|
f"http://{core_cfg.title.hostname}/{game_code}/$v/",
|
||||||
f"{core_cfg.title.hostname}/",
|
f"{core_cfg.title.hostname}",
|
||||||
)
|
)
|
||||||
|
|
||||||
def render_POST(self, request: Request, version: int, url_path: str) -> bytes:
|
def render_POST(self, request: Request, version: int, url_path: str) -> bytes:
|
||||||
|
@ -102,21 +119,50 @@ class Mai2Servlet:
|
||||||
endpoint = url_split[len(url_split) - 1]
|
endpoint = url_split[len(url_split) - 1]
|
||||||
client_ip = Utils.get_ip_addr(request)
|
client_ip = Utils.get_ip_addr(request)
|
||||||
|
|
||||||
if version < 105: # 1.0
|
if request.uri.startswith(b"/SDEZ"):
|
||||||
internal_ver = Mai2Constants.VER_MAIMAI_DX
|
if version < 105: # 1.0
|
||||||
elif version >= 105 and version < 110: # Plus
|
internal_ver = Mai2Constants.VER_MAIMAI_DX
|
||||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_PLUS
|
elif version >= 105 and version < 110: # Plus
|
||||||
elif version >= 110 and version < 115: # Splash
|
internal_ver = Mai2Constants.VER_MAIMAI_DX_PLUS
|
||||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_SPLASH
|
elif version >= 110 and version < 115: # Splash
|
||||||
elif version >= 115 and version < 120: # Splash Plus
|
internal_ver = Mai2Constants.VER_MAIMAI_DX_SPLASH
|
||||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_SPLASH_PLUS
|
elif version >= 115 and version < 120: # Splash Plus
|
||||||
elif version >= 120 and version < 125: # Universe
|
internal_ver = Mai2Constants.VER_MAIMAI_DX_SPLASH_PLUS
|
||||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_UNIVERSE
|
elif version >= 120 and version < 125: # Universe
|
||||||
elif version >= 125 and version < 130: # Universe Plus
|
internal_ver = Mai2Constants.VER_MAIMAI_DX_UNIVERSE
|
||||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_UNIVERSE_PLUS
|
elif version >= 125 and version < 130: # Universe Plus
|
||||||
elif version >= 130: # Festival
|
internal_ver = Mai2Constants.VER_MAIMAI_DX_UNIVERSE_PLUS
|
||||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_FESTIVAL
|
elif version >= 130: # Festival
|
||||||
|
internal_ver = Mai2Constants.VER_MAIMAI_DX_FESTIVAL
|
||||||
|
|
||||||
|
else:
|
||||||
|
if version < 110: # 1.0
|
||||||
|
internal_ver = Mai2Constants.VER_MAIMAI
|
||||||
|
elif version >= 110 and version < 120: # Plus
|
||||||
|
internal_ver = Mai2Constants.VER_MAIMAI_PLUS
|
||||||
|
elif version >= 120 and version < 130: # Green
|
||||||
|
internal_ver = Mai2Constants.VER_MAIMAI_GREEN
|
||||||
|
elif version >= 130 and version < 140: # Green Plus
|
||||||
|
internal_ver = Mai2Constants.VER_MAIMAI_GREEN_PLUS
|
||||||
|
elif version >= 140 and version < 150: # Orange
|
||||||
|
internal_ver = Mai2Constants.VER_MAIMAI_ORANGE
|
||||||
|
elif version >= 150 and version < 160: # Orange Plus
|
||||||
|
internal_ver = Mai2Constants.VER_MAIMAI_ORANGE_PLUS
|
||||||
|
elif version >= 160 and version < 170: # Pink
|
||||||
|
internal_ver = Mai2Constants.VER_MAIMAI_PINK
|
||||||
|
elif version >= 170 and version < 180: # Pink Plus
|
||||||
|
internal_ver = Mai2Constants.VER_MAIMAI_PINK_PLUS
|
||||||
|
elif version >= 180 and version < 185: # Murasaki
|
||||||
|
internal_ver = Mai2Constants.VER_MAIMAI_MURASAKI
|
||||||
|
elif version >= 185 and version < 190: # Murasaki Plus
|
||||||
|
internal_ver = Mai2Constants.VER_MAIMAI_MURASAKI_PLUS
|
||||||
|
elif version >= 190 and version < 195: # Milk
|
||||||
|
internal_ver = Mai2Constants.VER_MAIMAI_MILK
|
||||||
|
elif version >= 195 and version < 197: # Milk Plus
|
||||||
|
internal_ver = Mai2Constants.VER_MAIMAI_MILK_PLUS
|
||||||
|
elif version >= 197: # Finale
|
||||||
|
internal_ver = Mai2Constants.VER_MAIMAI_FINALE
|
||||||
|
|
||||||
if all(c in string.hexdigits for c in endpoint) and len(endpoint) == 32:
|
if all(c in string.hexdigits for c in endpoint) and len(endpoint) == 32:
|
||||||
# If we get a 32 character long hex string, it's a hash and we're
|
# If we get a 32 character long hex string, it's a hash and we're
|
||||||
# doing encrypted. The likelyhood of false positives is low but
|
# doing encrypted. The likelyhood of false positives is low but
|
||||||
|
@ -159,3 +205,33 @@ class Mai2Servlet:
|
||||||
self.logger.debug(f"Response {resp}")
|
self.logger.debug(f"Response {resp}")
|
||||||
|
|
||||||
return zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8"))
|
return zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8"))
|
||||||
|
|
||||||
|
def render_GET(self, request: Request, version: int, url_path: str) -> bytes:
|
||||||
|
self.logger.info(f"v{version} GET {url_path}")
|
||||||
|
url_split = url_path.split("/")
|
||||||
|
|
||||||
|
if url_split[0] == "old":
|
||||||
|
if url_split[1] == "ping":
|
||||||
|
self.logger.info(f"v{version} old server ping")
|
||||||
|
return zlib.compress(b"ok")
|
||||||
|
|
||||||
|
elif url_split[1].startswith("userdata"):
|
||||||
|
self.logger.info(f"v{version} old server userdata inquire")
|
||||||
|
return zlib.compress(b"{}")
|
||||||
|
|
||||||
|
elif url_split[1].startswith("friend"):
|
||||||
|
self.logger.info(f"v{version} old server friend inquire")
|
||||||
|
return zlib.compress(b"{}")
|
||||||
|
|
||||||
|
elif url_split[0] == "usbdl":
|
||||||
|
if url_split[1] == "CONNECTIONTEST":
|
||||||
|
self.logger.info(f"v{version} usbdl server test")
|
||||||
|
return zlib.compress(b"ok")
|
||||||
|
|
||||||
|
elif url_split[0] == "deliver":
|
||||||
|
if url_split[len(url_split) - 1] == "maimai_deliver.list":
|
||||||
|
self.logger.info(f"v{version} maimai_deliver.list inquire")
|
||||||
|
return zlib.compress(b"")
|
||||||
|
|
||||||
|
else:
|
||||||
|
return zlib.compress(b"{}")
|
||||||
|
|
|
@ -4,6 +4,9 @@ import os
|
||||||
import re
|
import re
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
|
from Crypto.Cipher import AES
|
||||||
|
import zlib
|
||||||
|
import codecs
|
||||||
|
|
||||||
from core.config import CoreConfig
|
from core.config import CoreConfig
|
||||||
from core.data import Data
|
from core.data import Data
|
||||||
|
@ -34,18 +37,148 @@ class Mai2Reader(BaseReader):
|
||||||
|
|
||||||
def read(self) -> None:
|
def read(self) -> None:
|
||||||
data_dirs = []
|
data_dirs = []
|
||||||
if self.bin_dir is not None:
|
if self.version < Mai2Constants.VER_MAIMAI:
|
||||||
data_dirs += self.get_data_directories(self.bin_dir)
|
if self.bin_dir is not None:
|
||||||
|
data_dirs += self.get_data_directories(self.bin_dir)
|
||||||
|
|
||||||
if self.opt_dir is not None:
|
if self.opt_dir is not None:
|
||||||
data_dirs += self.get_data_directories(self.opt_dir)
|
data_dirs += self.get_data_directories(self.opt_dir)
|
||||||
|
|
||||||
for dir in data_dirs:
|
for dir in data_dirs:
|
||||||
self.logger.info(f"Read from {dir}")
|
self.logger.info(f"Read from {dir}")
|
||||||
self.get_events(f"{dir}/event")
|
self.get_events(f"{dir}/event")
|
||||||
self.disable_events(f"{dir}/information", f"{dir}/scoreRanking")
|
self.disable_events(f"{dir}/information", f"{dir}/scoreRanking")
|
||||||
self.read_music(f"{dir}/music")
|
self.read_music(f"{dir}/music")
|
||||||
self.read_tickets(f"{dir}/ticket")
|
self.read_tickets(f"{dir}/ticket")
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.logger.warn("Pre-DX Readers are not yet implemented!")
|
||||||
|
if not os.path.exists(f"{self.bin_dir}/tables"):
|
||||||
|
self.logger.error(f"tables directory not found in {self.bin_dir}")
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.version >= Mai2Constants.VER_MAIMAI_MILK:
|
||||||
|
if self.extra is None:
|
||||||
|
self.logger.error("Milk - Finale requre an AES key via a hex string send as the --extra flag")
|
||||||
|
return
|
||||||
|
|
||||||
|
key = bytes.fromhex(self.extra)
|
||||||
|
|
||||||
|
else:
|
||||||
|
key = None
|
||||||
|
|
||||||
|
evt_table = self.load_table_raw(f"{self.bin_dir}/tables", "mmEvent.bin", key)
|
||||||
|
txt_table = self.load_table_raw(f"{self.bin_dir}/tables", "mmtextout_jp.bin", key)
|
||||||
|
score_table = self.load_table_raw(f"{self.bin_dir}/tables", "mmScore.bin", key)
|
||||||
|
|
||||||
|
self.read_old_events(evt_table)
|
||||||
|
self.read_old_music(score_table, txt_table)
|
||||||
|
|
||||||
|
if self.opt_dir is not None:
|
||||||
|
evt_table = self.load_table_raw(f"{self.opt_dir}/tables", "mmEvent.bin", key)
|
||||||
|
txt_table = self.load_table_raw(f"{self.opt_dir}/tables", "mmtextout_jp.bin", key)
|
||||||
|
score_table = self.load_table_raw(f"{self.opt_dir}/tables", "mmScore.bin", key)
|
||||||
|
|
||||||
|
self.read_old_events(evt_table)
|
||||||
|
self.read_old_music(score_table, txt_table)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def load_table_raw(self, dir: str, file: str, key: Optional[bytes]) -> Optional[List[Dict[str, str]]]:
|
||||||
|
if not os.path.exists(f"{dir}/{file}"):
|
||||||
|
self.logger.warn(f"file {file} does not exist in directory {dir}, skipping")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.logger.info(f"Load table {file} from {dir}")
|
||||||
|
if key is not None:
|
||||||
|
cipher = AES.new(key, AES.MODE_CBC)
|
||||||
|
with open(f"{dir}/{file}", "rb") as f:
|
||||||
|
f_encrypted = f.read()
|
||||||
|
f_data = cipher.decrypt(f_encrypted)[0x10:]
|
||||||
|
|
||||||
|
else:
|
||||||
|
with open(f"{dir}/{file}", "rb") as f:
|
||||||
|
f_data = f.read()[0x10:]
|
||||||
|
|
||||||
|
if f_data is None or not f_data:
|
||||||
|
self.logger.warn(f"file {dir} could not be read, skipping")
|
||||||
|
return
|
||||||
|
|
||||||
|
f_data_deflate = zlib.decompress(f_data, wbits = zlib.MAX_WBITS | 16)[0x12:] # lop off the junk at the beginning
|
||||||
|
f_decoded = codecs.utf_16_le_decode(f_data_deflate)[0]
|
||||||
|
f_split = f_decoded.splitlines()
|
||||||
|
|
||||||
|
has_struct_def = "struct " in f_decoded
|
||||||
|
is_struct = False
|
||||||
|
struct_def = []
|
||||||
|
tbl_content = []
|
||||||
|
|
||||||
|
if has_struct_def:
|
||||||
|
for x in f_split:
|
||||||
|
if x.startswith("struct "):
|
||||||
|
is_struct = True
|
||||||
|
struct_name = x[7:-1]
|
||||||
|
continue
|
||||||
|
|
||||||
|
if x.startswith("};"):
|
||||||
|
is_struct = False
|
||||||
|
break
|
||||||
|
|
||||||
|
if is_struct:
|
||||||
|
try:
|
||||||
|
struct_def.append(x[x.rindex(" ") + 2: -1])
|
||||||
|
except ValueError:
|
||||||
|
self.logger.warn(f"rindex failed on line {x}")
|
||||||
|
|
||||||
|
if is_struct:
|
||||||
|
self.logger.warn("Struct not formatted properly")
|
||||||
|
|
||||||
|
if not struct_def:
|
||||||
|
self.logger.warn("Struct def not found")
|
||||||
|
|
||||||
|
name = file[:file.index(".")]
|
||||||
|
if "_" in name:
|
||||||
|
name = name[:file.index("_")]
|
||||||
|
|
||||||
|
for x in f_split:
|
||||||
|
if not x.startswith(name.upper()):
|
||||||
|
continue
|
||||||
|
|
||||||
|
line_match = re.match(r"(\w+)\((.*?)\)([ ]+\/{3}<[ ]+(.*))?", x)
|
||||||
|
if line_match is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not line_match.group(1) == name.upper():
|
||||||
|
self.logger.warn(f"Strange regex match for line {x} -> {line_match}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
vals = line_match.group(2)
|
||||||
|
comment = line_match.group(4)
|
||||||
|
line_dict = {}
|
||||||
|
|
||||||
|
vals_split = vals.split(",")
|
||||||
|
for y in range(len(vals_split)):
|
||||||
|
stripped = vals_split[y].strip().lstrip("L\"").lstrip("\"").rstrip("\"")
|
||||||
|
if not stripped or stripped is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if has_struct_def and len(struct_def) > y:
|
||||||
|
line_dict[struct_def[y]] = stripped
|
||||||
|
|
||||||
|
else:
|
||||||
|
line_dict[f'item_{y}'] = stripped
|
||||||
|
|
||||||
|
if comment:
|
||||||
|
line_dict['comment'] = comment
|
||||||
|
|
||||||
|
tbl_content.append(line_dict)
|
||||||
|
|
||||||
|
if tbl_content:
|
||||||
|
return tbl_content
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.logger.warning("Failed load table content, skipping")
|
||||||
|
return
|
||||||
|
|
||||||
def get_events(self, base_dir: str) -> None:
|
def get_events(self, base_dir: str) -> None:
|
||||||
self.logger.info(f"Reading events from {base_dir}...")
|
self.logger.info(f"Reading events from {base_dir}...")
|
||||||
|
@ -188,3 +321,24 @@ class Mai2Reader(BaseReader):
|
||||||
self.version, id, ticket_type, price, name
|
self.version, id, ticket_type, price, name
|
||||||
)
|
)
|
||||||
self.logger.info(f"Added ticket {id}...")
|
self.logger.info(f"Added ticket {id}...")
|
||||||
|
|
||||||
|
def read_old_events(self, events: Optional[List[Dict[str, str]]]) -> None:
|
||||||
|
if events is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
for event in events:
|
||||||
|
evt_id = int(event.get('イベントID', '0'))
|
||||||
|
evt_expire_time = float(event.get('オフ時強制時期', '0.0'))
|
||||||
|
is_exp = bool(int(event.get('海外許可', '0')))
|
||||||
|
is_aou = bool(int(event.get('AOU許可', '0')))
|
||||||
|
name = event.get('comment', f'evt_{evt_id}')
|
||||||
|
|
||||||
|
self.data.static.put_game_event(self.version, 0, evt_id, name)
|
||||||
|
|
||||||
|
if not (is_exp or is_aou):
|
||||||
|
self.data.static.toggle_game_event(self.version, evt_id, False)
|
||||||
|
|
||||||
|
def read_old_music(self, scores: Optional[List[Dict[str, str]]], text: Optional[List[Dict[str, str]]]) -> None:
|
||||||
|
if scores is None or text is None:
|
||||||
|
return
|
||||||
|
# TODO
|
||||||
|
|
|
@ -4,12 +4,12 @@ import pytz
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from core.config import CoreConfig
|
from core.config import CoreConfig
|
||||||
from titles.mai2.base import Mai2Base
|
from titles.mai2.dx import Mai2DX
|
||||||
from titles.mai2.config import Mai2Config
|
from titles.mai2.config import Mai2Config
|
||||||
from titles.mai2.const import Mai2Constants
|
from titles.mai2.const import Mai2Constants
|
||||||
|
|
||||||
|
|
||||||
class Mai2Splash(Mai2Base):
|
class Mai2Splash(Mai2DX):
|
||||||
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
||||||
super().__init__(cfg, game_cfg)
|
super().__init__(cfg, game_cfg)
|
||||||
self.version = Mai2Constants.VER_MAIMAI_DX_SPLASH
|
self.version = Mai2Constants.VER_MAIMAI_DX_SPLASH
|
||||||
|
|
|
@ -4,12 +4,12 @@ import pytz
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from core.config import CoreConfig
|
from core.config import CoreConfig
|
||||||
from titles.mai2.base import Mai2Base
|
from titles.mai2.dx import Mai2DX
|
||||||
from titles.mai2.config import Mai2Config
|
from titles.mai2.config import Mai2Config
|
||||||
from titles.mai2.const import Mai2Constants
|
from titles.mai2.const import Mai2Constants
|
||||||
|
|
||||||
|
|
||||||
class Mai2SplashPlus(Mai2Base):
|
class Mai2SplashPlus(Mai2DX):
|
||||||
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
||||||
super().__init__(cfg, game_cfg)
|
super().__init__(cfg, game_cfg)
|
||||||
self.version = Mai2Constants.VER_MAIMAI_DX_SPLASH_PLUS
|
self.version = Mai2Constants.VER_MAIMAI_DX_SPLASH_PLUS
|
||||||
|
|
|
@ -5,185 +5,12 @@ import pytz
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from core.config import CoreConfig
|
from core.config import CoreConfig
|
||||||
from titles.mai2.base import Mai2Base
|
from titles.mai2.dx import Mai2DX
|
||||||
from titles.mai2.const import Mai2Constants
|
from titles.mai2.const import Mai2Constants
|
||||||
from titles.mai2.config import Mai2Config
|
from titles.mai2.config import Mai2Config
|
||||||
|
|
||||||
|
|
||||||
class Mai2Universe(Mai2Base):
|
class Mai2Universe(Mai2DX):
|
||||||
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
||||||
super().__init__(cfg, game_cfg)
|
super().__init__(cfg, game_cfg)
|
||||||
self.version = Mai2Constants.VER_MAIMAI_DX_UNIVERSE
|
self.version = Mai2Constants.VER_MAIMAI_DX_UNIVERSE
|
||||||
|
|
||||||
def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
|
|
||||||
p = self.data.profile.get_profile_detail(data["userId"], self.version)
|
|
||||||
if p is None:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
return {
|
|
||||||
"userName": p["userName"],
|
|
||||||
"rating": p["playerRating"],
|
|
||||||
# hardcode lastDataVersion for CardMaker 1.34
|
|
||||||
"lastDataVersion": "1.20.00",
|
|
||||||
"isLogin": False,
|
|
||||||
"isExistSellingCard": False,
|
|
||||||
}
|
|
||||||
|
|
||||||
def handle_cm_get_user_data_api_request(self, data: Dict) -> Dict:
|
|
||||||
# user already exists, because the preview checks that already
|
|
||||||
p = self.data.profile.get_profile_detail(data["userId"], self.version)
|
|
||||||
|
|
||||||
cards = self.data.card.get_user_cards(data["userId"])
|
|
||||||
if cards is None or len(cards) == 0:
|
|
||||||
# This should never happen
|
|
||||||
self.logger.error(
|
|
||||||
f"handle_get_user_data_api_request: Internal error - No cards found for user id {data['userId']}"
|
|
||||||
)
|
|
||||||
return {}
|
|
||||||
|
|
||||||
# get the dict representation of the row so we can modify values
|
|
||||||
user_data = p._asdict()
|
|
||||||
|
|
||||||
# remove the values the game doesn't want
|
|
||||||
user_data.pop("id")
|
|
||||||
user_data.pop("user")
|
|
||||||
user_data.pop("version")
|
|
||||||
|
|
||||||
return {"userId": data["userId"], "userData": user_data}
|
|
||||||
|
|
||||||
def handle_cm_login_api_request(self, data: Dict) -> Dict:
|
|
||||||
return {"returnCode": 1}
|
|
||||||
|
|
||||||
def handle_cm_logout_api_request(self, data: Dict) -> Dict:
|
|
||||||
return {"returnCode": 1}
|
|
||||||
|
|
||||||
def handle_cm_get_selling_card_api_request(self, data: Dict) -> Dict:
|
|
||||||
selling_cards = self.data.static.get_enabled_cards(self.version)
|
|
||||||
if selling_cards is None:
|
|
||||||
return {"length": 0, "sellingCardList": []}
|
|
||||||
|
|
||||||
selling_card_list = []
|
|
||||||
for card in selling_cards:
|
|
||||||
tmp = card._asdict()
|
|
||||||
tmp.pop("id")
|
|
||||||
tmp.pop("version")
|
|
||||||
tmp.pop("cardName")
|
|
||||||
tmp.pop("enabled")
|
|
||||||
|
|
||||||
tmp["startDate"] = datetime.strftime(tmp["startDate"], "%Y-%m-%d %H:%M:%S")
|
|
||||||
tmp["endDate"] = datetime.strftime(tmp["endDate"], "%Y-%m-%d %H:%M:%S")
|
|
||||||
tmp["noticeStartDate"] = datetime.strftime(
|
|
||||||
tmp["noticeStartDate"], "%Y-%m-%d %H:%M:%S"
|
|
||||||
)
|
|
||||||
tmp["noticeEndDate"] = datetime.strftime(
|
|
||||||
tmp["noticeEndDate"], "%Y-%m-%d %H:%M:%S"
|
|
||||||
)
|
|
||||||
|
|
||||||
selling_card_list.append(tmp)
|
|
||||||
|
|
||||||
return {"length": len(selling_card_list), "sellingCardList": selling_card_list}
|
|
||||||
|
|
||||||
def handle_cm_get_user_card_api_request(self, data: Dict) -> Dict:
|
|
||||||
user_cards = self.data.item.get_cards(data["userId"])
|
|
||||||
if user_cards is None:
|
|
||||||
return {"returnCode": 1, "length": 0, "nextIndex": 0, "userCardList": []}
|
|
||||||
|
|
||||||
max_ct = data["maxCount"]
|
|
||||||
next_idx = data["nextIndex"]
|
|
||||||
start_idx = next_idx
|
|
||||||
end_idx = max_ct + start_idx
|
|
||||||
|
|
||||||
if len(user_cards[start_idx:]) > max_ct:
|
|
||||||
next_idx += max_ct
|
|
||||||
else:
|
|
||||||
next_idx = 0
|
|
||||||
|
|
||||||
card_list = []
|
|
||||||
for card in user_cards:
|
|
||||||
tmp = card._asdict()
|
|
||||||
tmp.pop("id")
|
|
||||||
tmp.pop("user")
|
|
||||||
|
|
||||||
tmp["startDate"] = datetime.strftime(tmp["startDate"], "%Y-%m-%d %H:%M:%S")
|
|
||||||
tmp["endDate"] = datetime.strftime(tmp["endDate"], "%Y-%m-%d %H:%M:%S")
|
|
||||||
card_list.append(tmp)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"returnCode": 1,
|
|
||||||
"length": len(card_list[start_idx:end_idx]),
|
|
||||||
"nextIndex": next_idx,
|
|
||||||
"userCardList": card_list[start_idx:end_idx],
|
|
||||||
}
|
|
||||||
|
|
||||||
def handle_cm_get_user_item_api_request(self, data: Dict) -> Dict:
|
|
||||||
super().handle_get_user_item_api_request(data)
|
|
||||||
|
|
||||||
def handle_cm_get_user_character_api_request(self, data: Dict) -> Dict:
|
|
||||||
characters = self.data.item.get_characters(data["userId"])
|
|
||||||
|
|
||||||
chara_list = []
|
|
||||||
for chara in characters:
|
|
||||||
chara_list.append(
|
|
||||||
{
|
|
||||||
"characterId": chara["characterId"],
|
|
||||||
# no clue why those values are even needed
|
|
||||||
"point": 0,
|
|
||||||
"count": 0,
|
|
||||||
"level": chara["level"],
|
|
||||||
"nextAwake": 0,
|
|
||||||
"nextAwakePercent": 0,
|
|
||||||
"favorite": False,
|
|
||||||
"awakening": chara["awakening"],
|
|
||||||
"useCount": chara["useCount"],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"returnCode": 1,
|
|
||||||
"length": len(chara_list),
|
|
||||||
"userCharacterList": chara_list,
|
|
||||||
}
|
|
||||||
|
|
||||||
def handle_cm_get_user_card_print_error_api_request(self, data: Dict) -> Dict:
|
|
||||||
return {"length": 0, "userPrintDetailList": []}
|
|
||||||
|
|
||||||
def handle_cm_upsert_user_print_api_request(self, data: Dict) -> Dict:
|
|
||||||
user_id = data["userId"]
|
|
||||||
upsert = data["userPrintDetail"]
|
|
||||||
|
|
||||||
# set a random card serial number
|
|
||||||
serial_id = "".join([str(randint(0, 9)) for _ in range(20)])
|
|
||||||
|
|
||||||
user_card = upsert["userCard"]
|
|
||||||
self.data.item.put_card(
|
|
||||||
user_id,
|
|
||||||
user_card["cardId"],
|
|
||||||
user_card["cardTypeId"],
|
|
||||||
user_card["charaId"],
|
|
||||||
user_card["mapId"],
|
|
||||||
)
|
|
||||||
|
|
||||||
# properly format userPrintDetail for the database
|
|
||||||
upsert.pop("userCard")
|
|
||||||
upsert.pop("serialId")
|
|
||||||
upsert["printDate"] = datetime.strptime(upsert["printDate"], "%Y-%m-%d")
|
|
||||||
|
|
||||||
self.data.item.put_user_print_detail(user_id, serial_id, upsert)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"returnCode": 1,
|
|
||||||
"orderId": 0,
|
|
||||||
"serialId": serial_id,
|
|
||||||
"startDate": "2018-01-01 00:00:00",
|
|
||||||
"endDate": "2038-01-01 00:00:00",
|
|
||||||
}
|
|
||||||
|
|
||||||
def handle_cm_upsert_user_printlog_api_request(self, data: Dict) -> Dict:
|
|
||||||
return {
|
|
||||||
"returnCode": 1,
|
|
||||||
"orderId": 0,
|
|
||||||
"serialId": data["userPrintlog"]["serialId"],
|
|
||||||
}
|
|
||||||
|
|
||||||
def handle_cm_upsert_buy_card_api_request(self, data: Dict) -> Dict:
|
|
||||||
return {"returnCode": 1}
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from core.config import CoreConfig
|
from core.config import CoreConfig
|
||||||
from titles.mai2.universe import Mai2Universe
|
from titles.mai2.dx import Mai2DX
|
||||||
from titles.mai2.const import Mai2Constants
|
from titles.mai2.const import Mai2Constants
|
||||||
from titles.mai2.config import Mai2Config
|
from titles.mai2.config import Mai2Config
|
||||||
|
|
||||||
|
|
||||||
class Mai2UniversePlus(Mai2Universe):
|
class Mai2UniversePlus(Mai2DX):
|
||||||
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
||||||
super().__init__(cfg, game_cfg)
|
super().__init__(cfg, game_cfg)
|
||||||
self.version = Mai2Constants.VER_MAIMAI_DX_UNIVERSE_PLUS
|
self.version = Mai2Constants.VER_MAIMAI_DX_UNIVERSE_PLUS
|
||||||
|
|
Loading…
Reference in New Issue