1
0
Fork 0

Merge pull request 'maimai DX FESTiVAL support' (#17) from Dniel97/artemis:maimai_dx_festival into develop

Reviewed-on: Hay1tsme/artemis#17
This commit is contained in:
Midorica 2023-04-19 18:13:31 +00:00
commit 017ef1e224
17 changed files with 432 additions and 212 deletions

View File

@ -0,0 +1,31 @@
ALTER TABLE mai2_profile_option
DROP COLUMN tapSe;
ALTER TABLE mai2_score_best
DROP COLUMN extNum1;
ALTER TABLE mai2_profile_extend
DROP COLUMN playStatusSetting;
ALTER TABLE mai2_playlog
DROP COLUMN extNum4;
ALTER TABLE mai2_static_event
DROP COLUMN startDate;
ALTER TABLE mai2_item_map
CHANGE COLUMN mapId map_id INT NOT NULL,
CHANGE COLUMN isLock is_lock BOOLEAN NOT NULL DEFAULT 0,
CHANGE COLUMN isClear is_clear BOOLEAN NOT NULL DEFAULT 0,
CHANGE COLUMN isComplete is_complete BOOLEAN NOT NULL DEFAULT 0;
ALTER TABLE mai2_item_friend_season_ranking
CHANGE COLUMN seasonId season_id INT NOT NULL,
CHANGE COLUMN rewardGet reward_get BOOLEAN NOT NULL,
CHANGE COLUMN userName user_name VARCHAR(8) NOT NULL,
CHANGE COLUMN recordDate record_date VARCHAR(255) NOT NULL;
ALTER TABLE mai2_item_login_bonus
CHANGE COLUMN bonusId bonus_id INT NOT NULL,
CHANGE COLUMN isCurrent is_current BOOLEAN NOT NULL DEFAULT 0,
CHANGE COLUMN isComplete is_complete BOOLEAN NOT NULL DEFAULT 0;

View File

@ -0,0 +1,31 @@
ALTER TABLE mai2_profile_option
ADD COLUMN tapSe INT NOT NULL DEFAULT 0 AFTER tapDesign;
ALTER TABLE mai2_score_best
ADD COLUMN extNum1 INT NOT NULL DEFAULT 0;
ALTER TABLE mai2_profile_extend
ADD COLUMN playStatusSetting INT NOT NULL DEFAULT 0;
ALTER TABLE mai2_playlog
ADD COLUMN extNum4 INT NOT NULL DEFAULT 0;
ALTER TABLE mai2_static_event
ADD COLUMN startDate TIMESTAMP NOT NULL DEFAULT current_timestamp();
ALTER TABLE mai2_item_map
CHANGE COLUMN map_id mapId INT NOT NULL,
CHANGE COLUMN is_lock isLock BOOLEAN NOT NULL DEFAULT 0,
CHANGE COLUMN is_clear isClear BOOLEAN NOT NULL DEFAULT 0,
CHANGE COLUMN is_complete isComplete BOOLEAN NOT NULL DEFAULT 0;
ALTER TABLE mai2_item_friend_season_ranking
CHANGE COLUMN season_id seasonId INT NOT NULL,
CHANGE COLUMN reward_get rewardGet BOOLEAN NOT NULL,
CHANGE COLUMN user_name userName VARCHAR(8) NOT NULL,
CHANGE COLUMN record_date recordDate TIMESTAMP NOT NULL;
ALTER TABLE mai2_item_login_bonus
CHANGE COLUMN bonus_id bonusId INT NOT NULL,
CHANGE COLUMN is_current isCurrent BOOLEAN NOT NULL DEFAULT 0,
CHANGE COLUMN is_complete isComplete BOOLEAN NOT NULL DEFAULT 0;

View File

@ -113,6 +113,7 @@ Config file is located in `config/cxb.yaml`.
| 3 | maimai DX Splash PLUS |
| 4 | maimai DX Universe |
| 5 | maimai DX Universe PLUS |
| 6 | maimai DX Festival |
### Importer
@ -125,7 +126,7 @@ python read.py --series SDEZ --version <version ID> --binfolder /path/to/game/fo
The importer for maimai DX will import Events, Music and Tickets.
**NOTE: It is required to use the importer because the game will
crash without it!**
crash without Events!**
### Database upgrade

View File

@ -9,8 +9,8 @@ Games listed below have been tested and confirmed working. Only game versions ol
+ Crossbeats Rev
+ All versions + omnimix
+ Maimai
+ All versions up to Universe Plus
+ maimai DX
+ All versions up to Festival
+ Hatsune Miku Arcade
+ All versions

View File

@ -12,6 +12,7 @@ from twisted.web.http import Request
from logging.handlers import TimedRotatingFileHandler
from core.config import CoreConfig
from core.utils import Utils
from titles.cm.config import CardMakerConfig
from titles.cm.const import CardMakerConstants
from titles.cm.base import CardMakerBase
@ -82,6 +83,7 @@ class CardMakerServlet:
url_split = url_path.split("/")
internal_ver = 0
endpoint = url_split[len(url_split) - 1]
client_ip = Utils.get_ip_addr(request)
print(f"version: {version}")
@ -107,7 +109,10 @@ class CardMakerServlet:
req_data = json.loads(unzip)
self.logger.info(f"v{version} {endpoint} request - {req_data}")
self.logger.info(
f"v{version} {endpoint} request from {client_ip}"
)
self.logger.debug(req_data)
func_to_find = "handle_" + inflection.underscore(endpoint) + "_request"

View File

@ -80,7 +80,7 @@ class CardMakerReader(BaseReader):
for dir in data_dirs:
self.read_chuni_card(f"{dir}/CHU/card")
self.read_chuni_gacha(f"{dir}/CHU/gacha")
self.read_mai2_card(f"{dir}/MAI/card")
self.read_ongeki_gacha(f"{dir}/MU3/gacha")
def read_chuni_card(self, base_dir: str) -> None:
@ -206,6 +206,7 @@ class CardMakerReader(BaseReader):
"1.15": Mai2Constants.VER_MAIMAI_DX_SPLASH_PLUS,
"1.20": Mai2Constants.VER_MAIMAI_DX_UNIVERSE,
"1.25": Mai2Constants.VER_MAIMAI_DX_UNIVERSE_PLUS,
"1.30": Mai2Constants.VER_MAIMAI_DX_FESTIVAL,
}
for root, dirs, files in os.walk(base_dir):

View File

@ -7,4 +7,4 @@ index = Mai2Servlet
database = Mai2Data
reader = Mai2Reader
game_codes = [Mai2Constants.GAME_CODE]
current_schema_version = 3
current_schema_version = 4

View File

@ -1,5 +1,5 @@
from datetime import datetime, date, timedelta
from typing import Dict
from typing import Any, Dict
import logging
from core.config import CoreConfig
@ -52,6 +52,7 @@ class Mai2Base:
events = self.data.static.get_enabled_events(self.version)
events_lst = []
if events is None:
self.logger.warn("No enabled events, did you run the reader?")
return {"type": data["type"], "length": 0, "gameEventList": []}
for event in events:
@ -59,7 +60,11 @@ class Mai2Base:
{
"type": event["type"],
"id": event["eventId"],
"startDate": "2017-12-05 07:00:00.0",
# actually use the startDate from the import so it
# properly shows all the events when new ones are imported
"startDate": datetime.strftime(
event["startDate"], f"{Mai2Constants.DATE_TIME_FORMAT}.0"
),
"endDate": "2099-12-31 00:00:00.0",
}
)
@ -79,12 +84,12 @@ class Mai2Base:
return {"length": 0, "gameChargeList": []}
charge_list = []
for x in range(len(game_charge_list)):
for i, charge in enumerate(game_charge_list):
charge_list.append(
{
"orderId": x,
"chargeId": game_charge_list[x]["ticketId"],
"price": game_charge_list[x]["price"],
"orderId": i,
"chargeId": charge["ticketId"],
"price": charge["price"],
"startDate": "2017-12-05 07:00:00.0",
"endDate": "2099-12-31 00:00:00.0",
}
@ -93,16 +98,16 @@ class Mai2Base:
return {"length": len(charge_list), "gameChargeList": charge_list}
def handle_upsert_client_setting_api_request(self, data: Dict) -> Dict:
pass
return {"returnCode": 1, "apiName": "UpsertClientSettingApi"}
def handle_upsert_client_upload_api_request(self, data: Dict) -> Dict:
pass
return {"returnCode": 1, "apiName": "UpsertClientUploadApi"}
def handle_upsert_client_bookkeeping_api_request(self, data: Dict) -> Dict:
pass
return {"returnCode": 1, "apiName": "UpsertClientBookkeepingApi"}
def handle_upsert_client_testmode_api_request(self, data: Dict) -> Dict:
pass
return {"returnCode": 1, "apiName": "UpsertClientTestmodeApi"}
def handle_get_user_preview_api_request(self, data: Dict) -> Dict:
p = self.data.profile.get_profile_detail(data["userId"], self.version)
@ -167,6 +172,24 @@ class Mai2Base:
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"]
@ -204,15 +227,21 @@ class Mai2Base:
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"], "%Y-%m-%d %H:%M:%S"),
datetime.strptime(charge["validDate"], "%Y-%m-%d %H:%M:%S")
datetime.strptime(
charge["purchaseDate"], Mai2Constants.DATE_TIME_FORMAT
),
datetime.strptime(
charge["validDate"], Mai2Constants.DATE_TIME_FORMAT
),
)
if upsert["isNewCharacterList"] and int(upsert["isNewCharacterList"]) > 0:
if "userCharacterList" in upsert and len(upsert["userCharacterList"]) > 0:
for char in upsert["userCharacterList"]:
self.data.item.put_character(
user_id,
@ -222,7 +251,7 @@ class Mai2Base:
char["useCount"],
)
if upsert["isNewItemList"] and int(upsert["isNewItemList"]) > 0:
if "userItemList" in upsert and len(upsert["userItemList"]) > 0:
for item in upsert["userItemList"]:
self.data.item.put_item(
user_id,
@ -232,7 +261,7 @@ class Mai2Base:
item["isValid"],
)
if upsert["isNewLoginBonusList"] and int(upsert["isNewLoginBonusList"]) > 0:
if "userLoginBonusList" in upsert and len(upsert["userLoginBonusList"]) > 0:
for login_bonus in upsert["userLoginBonusList"]:
self.data.item.put_login_bonus(
user_id,
@ -242,7 +271,7 @@ class Mai2Base:
login_bonus["isComplete"],
)
if upsert["isNewMapList"] and int(upsert["isNewMapList"]) > 0:
if "userMapList" in upsert and len(upsert["userMapList"]) > 0:
for map in upsert["userMapList"]:
self.data.item.put_map(
user_id,
@ -253,24 +282,32 @@ class Mai2Base:
map["isComplete"],
)
if upsert["isNewMusicDetailList"] and int(upsert["isNewMusicDetailList"]) > 0:
if "userMusicDetailList" in upsert and len(upsert["userMusicDetailList"]) > 0:
for music in upsert["userMusicDetailList"]:
self.data.score.put_best_score(user_id, music)
if upsert["isNewCourseList"] and int(upsert["isNewCourseList"]) > 0:
if "userCourseList" in upsert and len(upsert["userCourseList"]) > 0:
for course in upsert["userCourseList"]:
self.data.score.put_course(user_id, course)
if upsert["isNewFavoriteList"] and int(upsert["isNewFavoriteList"]) > 0:
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 "isNewFriendSeasonRankingList" in upsert and int(upsert["isNewFriendSeasonRankingList"]) > 0:
# for fsr in upsert["userFriendSeasonRankingList"]:
# pass
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:
pass
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)
@ -311,11 +348,7 @@ class Mai2Base:
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": []
}
return {"userId": data["userId"], "nextIndex": 0, "userCardList": []}
max_ct = data["maxCount"]
next_idx = data["nextIndex"]
@ -333,25 +366,23 @@ class Mai2Base:
tmp.pop("id")
tmp.pop("user")
tmp["startDate"] = datetime.strftime(
tmp["startDate"], "%Y-%m-%d %H:%M:%S")
tmp["startDate"], Mai2Constants.DATE_TIME_FORMAT
)
tmp["endDate"] = datetime.strftime(
tmp["endDate"], "%Y-%m-%d %H:%M:%S")
tmp["endDate"], Mai2Constants.DATE_TIME_FORMAT
)
card_list.append(tmp)
return {
"userId": data["userId"],
"nextIndex": next_idx,
"userCardList": card_list[start_idx:end_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": []
}
return {"userId": data["userId"], "length": 0, "userChargeList": []}
user_charge_list = []
for charge in user_charges:
@ -359,45 +390,46 @@ class Mai2Base:
tmp.pop("id")
tmp.pop("user")
tmp["purchaseDate"] = datetime.strftime(
tmp["purchaseDate"], "%Y-%m-%d %H:%M:%S")
tmp["purchaseDate"], Mai2Constants.DATE_TIME_FORMAT
)
tmp["validDate"] = datetime.strftime(
tmp["validDate"], "%Y-%m-%d %H:%M:%S")
tmp["validDate"], Mai2Constants.DATE_TIME_FORMAT
)
user_charge_list.append(tmp)
return {
"userId": data["userId"],
"length": len(user_charge_list),
"userChargeList": 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_items = self.data.item.get_items(data["userId"], kind)
user_item_list = []
next_idx = 0
user_item_list = self.data.item.get_items(data["userId"], kind)
for x in range(next_idx, data["maxCount"]):
try:
user_item_list.append({
"itemKind": user_items[x]["itemKind"],
"itemId": user_items[x]["itemId"],
"stock": user_items[x]["stock"],
"isValid": user_items[x]["isValid"]
})
except IndexError:
items: list[Dict[str, Any]] = []
for i in range(next_idx, len(user_item_list)):
tmp = user_item_list[i]._asdict()
tmp.pop("user")
tmp.pop("id")
items.append(tmp)
if len(items) >= int(data["maxCount"]):
break
if len(user_item_list) == data["maxCount"]:
next_idx = data["nextIndex"] + data["maxCount"] + 1
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": user_item_list
"userItemList": items,
}
def handle_get_user_character_api_request(self, data: Dict) -> Dict:
@ -479,21 +511,12 @@ class Mai2Base:
tmp.pop("user")
mlst.append(tmp)
return {
"userActivity": {
"playList": plst,
"musicList": mlst
}
}
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": []
}
return {"userId": data["userId"], "nextIndex": 0, "userCourseList": []}
course_list = []
for course in user_courses:
@ -502,11 +525,7 @@ class Mai2Base:
tmp.pop("id")
course_list.append(tmp)
return {
"userId": data["userId"],
"nextIndex": 0,
"userCourseList": course_list
}
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
@ -514,96 +533,103 @@ class Mai2Base:
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"])
friend_season_ranking_list = []
next_index = 0
if friend_season_ranking is None:
return {
"userId": data["userId"],
"nextIndex": 0,
"userFriendSeasonRankingList": [],
}
for x in range(data["nextIndex"], data["maxCount"] + data["nextIndex"]):
try:
friend_season_ranking_list.append(
{
"mapId": friend_season_ranking_list[x]["map_id"],
"distance": friend_season_ranking_list[x]["distance"],
"isLock": friend_season_ranking_list[x]["is_lock"],
"isClear": friend_season_ranking_list[x]["is_clear"],
"isComplete": friend_season_ranking_list[x]["is_complete"],
}
)
except:
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
# We're capped and still have some left to go
if (
len(friend_season_ranking_list) == data["maxCount"]
and len(friend_season_ranking) > data["maxCount"] + data["nextIndex"]
):
next_index = data["maxCount"] + data["nextIndex"]
if len(friend_season_ranking) >= next_idx + max_ct:
next_idx += max_ct
else:
next_idx = 0
return {
"userId": data["userId"],
"nextIndex": next_index,
"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"])
map_list = []
next_index = 0
if maps is None:
return {
"userId": data["userId"],
"nextIndex": 0,
"userMapList": [],
}
for x in range(data["nextIndex"], data["maxCount"] + data["nextIndex"]):
try:
map_list.append(
{
"mapId": maps[x]["map_id"],
"distance": maps[x]["distance"],
"isLock": maps[x]["is_lock"],
"isClear": maps[x]["is_clear"],
"isComplete": maps[x]["is_complete"],
}
)
except:
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
# We're capped and still have some left to go
if (
len(map_list) == data["maxCount"]
and len(maps) > data["maxCount"] + data["nextIndex"]
):
next_index = data["maxCount"] + data["nextIndex"]
if len(maps) >= next_idx + max_ct:
next_idx += max_ct
else:
next_idx = 0
return {
"userId": data["userId"],
"nextIndex": next_index,
"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"])
login_bonus_list = []
next_index = 0
if login_bonuses is None:
return {
"userId": data["userId"],
"nextIndex": 0,
"userLoginBonusList": [],
}
for x in range(data["nextIndex"], data["maxCount"] + data["nextIndex"]):
try:
login_bonus_list.append(
{
"bonusId": login_bonuses[x]["bonus_id"],
"point": login_bonuses[x]["point"],
"isCurrent": login_bonuses[x]["is_current"],
"isComplete": login_bonuses[x]["is_complete"],
}
)
except:
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
# We're capped and still have some left to go
if (
len(login_bonus_list) == data["maxCount"]
and len(login_bonuses) > data["maxCount"] + data["nextIndex"]
):
next_index = data["maxCount"] + data["nextIndex"]
if len(login_bonuses) >= next_idx + max_ct:
next_idx += max_ct
else:
next_idx = 0
return {
"userId": data["userId"],
"nextIndex": next_index,
"nextIndex": next_idx,
"userLoginBonusList": login_bonus_list,
}
@ -629,5 +655,5 @@ class Mai2Base:
return {
"userId": data["userId"],
"nextIndex": next_index,
"userMusicList": [{"userMusicDetailList": music_detail_list}]
"userMusicList": [{"userMusicDetailList": music_detail_list}],
}

View File

@ -30,14 +30,16 @@ class Mai2Constants:
VER_MAIMAI_DX_SPLASH_PLUS = 3
VER_MAIMAI_DX_UNIVERSE = 4
VER_MAIMAI_DX_UNIVERSE_PLUS = 5
VER_MAIMAI_DX_FESTIVAL = 6
VERSION_STRING = (
"maimai Delux",
"maimai Delux PLUS",
"maimai Delux Splash",
"maimai Delux Splash PLUS",
"maimai Delux Universe",
"maimai Delux Universe PLUS",
"maimai DX",
"maimai DX PLUS",
"maimai DX Splash",
"maimai DX Splash PLUS",
"maimai DX Universe",
"maimai DX Universe PLUS",
"maimai DX Festival"
)
@classmethod

31
titles/mai2/festival.py Normal file
View File

@ -0,0 +1,31 @@
from typing import Dict
from core.config import CoreConfig
from titles.mai2.universeplus import Mai2UniversePlus
from titles.mai2.const import Mai2Constants
from titles.mai2.config import Mai2Config
class Mai2Festival(Mai2UniversePlus):
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
super().__init__(cfg, game_cfg)
self.version = Mai2Constants.VER_MAIMAI_DX_FESTIVAL
def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
user_data = super().handle_cm_get_user_preview_api_request(data)
# hardcode lastDataVersion for CardMaker 1.36
user_data["lastDataVersion"] = "1.30.00"
return user_data
def handle_user_login_api_request(self, data: Dict) -> Dict:
user_login = super().handle_user_login_api_request(data)
# useless?
user_login["Bearer"] = "ARTEMiSTOKEN"
return user_login
def handle_get_user_recommend_rate_music_api_request(self, data: Dict) -> Dict:
return {"userId": data["userId"], "userRecommendRateMusicIdList": []}
def handle_get_user_recommend_select_music_api_request(self, data: Dict) -> Dict:
return {"userId": data["userId"], "userRecommendSelectionMusicIdList": []}

View File

@ -10,6 +10,7 @@ from os import path
from typing import Tuple
from core.config import CoreConfig
from core.utils import Utils
from titles.mai2.config import Mai2Config
from titles.mai2.const import Mai2Constants
from titles.mai2.base import Mai2Base
@ -18,6 +19,7 @@ from titles.mai2.splash import Mai2Splash
from titles.mai2.splashplus import Mai2SplashPlus
from titles.mai2.universe import Mai2Universe
from titles.mai2.universeplus import Mai2UniversePlus
from titles.mai2.festival import Mai2Festival
class Mai2Servlet:
@ -30,12 +32,13 @@ class Mai2Servlet:
)
self.versions = [
Mai2Base(core_cfg, self.game_cfg),
Mai2Plus(core_cfg, self.game_cfg),
Mai2Splash(core_cfg, self.game_cfg),
Mai2SplashPlus(core_cfg, self.game_cfg),
Mai2Universe(core_cfg, self.game_cfg),
Mai2UniversePlus(core_cfg, self.game_cfg),
Mai2Base,
Mai2Plus,
Mai2Splash,
Mai2SplashPlus,
Mai2Universe,
Mai2UniversePlus,
Mai2Festival
]
self.logger = logging.getLogger("mai2")
@ -97,6 +100,7 @@ class Mai2Servlet:
url_split = url_path.split("/")
internal_ver = 0
endpoint = url_split[len(url_split) - 1]
client_ip = Utils.get_ip_addr(request)
if version < 105: # 1.0
internal_ver = Mai2Constants.VER_MAIMAI_DX
@ -108,8 +112,10 @@ class Mai2Servlet:
internal_ver = Mai2Constants.VER_MAIMAI_DX_SPLASH_PLUS
elif version >= 120 and version < 125: # Universe
internal_ver = Mai2Constants.VER_MAIMAI_DX_UNIVERSE
elif version >= 125: # Universe Plus
elif version >= 125 and version < 130: # Universe Plus
internal_ver = Mai2Constants.VER_MAIMAI_DX_UNIVERSE_PLUS
elif version >= 130: # Festival
internal_ver = Mai2Constants.VER_MAIMAI_DX_FESTIVAL
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
@ -128,25 +134,30 @@ class Mai2Servlet:
req_data = json.loads(unzip)
self.logger.info(f"v{version} {endpoint} request - {req_data}")
self.logger.info(
f"v{version} {endpoint} request from {client_ip}"
)
self.logger.debug(req_data)
func_to_find = "handle_" + inflection.underscore(endpoint) + "_request"
handler_cls = self.versions[internal_ver](self.core_cfg, self.game_cfg)
if not hasattr(self.versions[internal_ver], func_to_find):
if not hasattr(handler_cls, func_to_find):
self.logger.warning(f"Unhandled v{version} request {endpoint}")
return zlib.compress(b'{"returnCode": 1}')
resp = {"returnCode": 1}
try:
handler = getattr(self.versions[internal_ver], func_to_find)
resp = handler(req_data)
else:
try:
handler = getattr(handler_cls, func_to_find)
resp = handler(req_data)
except Exception as e:
self.logger.error(f"Error handling v{version} method {endpoint} - {e}")
return zlib.compress(b'{"stat": "0"}')
except Exception as e:
self.logger.error(f"Error handling v{version} method {endpoint} - {e}")
return zlib.compress(b'{"stat": "0"}')
if resp == None:
resp = {"returnCode": 1}
self.logger.info(f"Response {resp}")
self.logger.debug(f"Response {resp}")
return zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8"))

View File

@ -29,7 +29,7 @@ class Mai2Reader(BaseReader):
f"Start importer for {Mai2Constants.game_ver_to_string(version)}"
)
except IndexError:
self.logger.error(f"Invalid maidx version {version}")
self.logger.error(f"Invalid maimai DX version {version}")
exit(1)
def read(self) -> None:
@ -43,6 +43,7 @@ class Mai2Reader(BaseReader):
for dir in data_dirs:
self.logger.info(f"Read from {dir}")
self.get_events(f"{dir}/event")
self.disable_events(f"{dir}/information", f"{dir}/scoreRanking")
self.read_music(f"{dir}/music")
self.read_tickets(f"{dir}/ticket")
@ -64,6 +65,64 @@ class Mai2Reader(BaseReader):
)
self.logger.info(f"Added event {id}...")
def disable_events(
self, base_information_dir: str, base_score_ranking_dir: str
) -> None:
self.logger.info(f"Reading disabled events from {base_information_dir}...")
for root, dirs, files in os.walk(base_information_dir):
for dir in dirs:
if os.path.exists(f"{root}/{dir}/Information.xml"):
with open(f"{root}/{dir}/Information.xml", encoding="utf-8") as f:
troot = ET.fromstring(f.read())
event_id = int(troot.find("name").find("id").text)
self.data.static.toggle_game_event(
self.version, event_id, toggle=False
)
self.logger.info(f"Disabled event {event_id}...")
for root, dirs, files in os.walk(base_score_ranking_dir):
for dir in dirs:
if os.path.exists(f"{root}/{dir}/ScoreRanking.xml"):
with open(f"{root}/{dir}/ScoreRanking.xml", encoding="utf-8") as f:
troot = ET.fromstring(f.read())
event_id = int(troot.find("eventName").find("id").text)
self.data.static.toggle_game_event(
self.version, event_id, toggle=False
)
self.logger.info(f"Disabled event {event_id}...")
# manually disable events wich are known to be problematic
for event_id in [
1,
10,
220311,
220312,
220313,
220314,
220315,
220316,
220317,
220318,
20121821,
21121651,
22091511,
22091512,
22091513,
22091514,
22091515,
22091516,
22091517,
22091518,
22091519,
]:
self.data.static.toggle_game_event(self.version, event_id, toggle=False)
self.logger.info(f"Disabled event {event_id}...")
def read_music(self, base_dir: str) -> None:
self.logger.info(f"Reading music from {base_dir}...")

View File

@ -71,12 +71,12 @@ map = Table(
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("map_id", Integer, nullable=False),
Column("mapId", Integer, nullable=False),
Column("distance", Integer, nullable=False),
Column("is_lock", Boolean, nullable=False, server_default="0"),
Column("is_clear", Boolean, nullable=False, server_default="0"),
Column("is_complete", Boolean, nullable=False, server_default="0"),
UniqueConstraint("user", "map_id", name="mai2_item_map_uk"),
Column("isLock", Boolean, nullable=False, server_default="0"),
Column("isClear", Boolean, nullable=False, server_default="0"),
Column("isComplete", Boolean, nullable=False, server_default="0"),
UniqueConstraint("user", "mapId", name="mai2_item_map_uk"),
mysql_charset="utf8mb4",
)
@ -89,11 +89,11 @@ login_bonus = Table(
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("bonus_id", Integer, nullable=False),
Column("bonusId", Integer, nullable=False),
Column("point", Integer, nullable=False),
Column("is_current", Boolean, nullable=False, server_default="0"),
Column("is_complete", Boolean, nullable=False, server_default="0"),
UniqueConstraint("user", "bonus_id", name="mai2_item_login_bonus_uk"),
Column("isCurrent", Boolean, nullable=False, server_default="0"),
Column("isComplete", Boolean, nullable=False, server_default="0"),
UniqueConstraint("user", "bonusId", name="mai2_item_login_bonus_uk"),
mysql_charset="utf8mb4",
)
@ -106,13 +106,15 @@ friend_season_ranking = Table(
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("season_id", Integer, nullable=False),
Column("seasonId", Integer, nullable=False),
Column("point", Integer, nullable=False),
Column("rank", Integer, nullable=False),
Column("reward_get", Boolean, nullable=False),
Column("user_name", String(8), nullable=False),
Column("record_date", String(255), nullable=False),
UniqueConstraint("user", "season_id", "user_name", name="mai2_item_login_bonus_uk"),
Column("rewardGet", Boolean, nullable=False),
Column("userName", String(8), nullable=False),
Column("recordDate", TIMESTAMP, nullable=False),
UniqueConstraint(
"user", "seasonId", "userName", name="mai2_item_friend_season_ranking_uk"
),
mysql_charset="utf8mb4",
)
@ -244,16 +246,16 @@ class Mai2ItemData(BaseData):
) -> None:
sql = insert(login_bonus).values(
user=user_id,
bonus_id=bonus_id,
bonusId=bonus_id,
point=point,
is_current=is_current,
is_complete=is_complete,
isCurrent=is_current,
isComplete=is_complete,
)
conflict = sql.on_duplicate_key_update(
point=point,
is_current=is_current,
is_complete=is_complete,
isCurrent=is_current,
isComplete=is_complete,
)
result = self.execute(conflict)
@ -293,18 +295,18 @@ class Mai2ItemData(BaseData):
) -> None:
sql = insert(map).values(
user=user_id,
map_id=map_id,
mapId=map_id,
distance=distance,
is_lock=is_lock,
is_clear=is_clear,
is_complete=is_complete,
isLock=is_lock,
isClear=is_clear,
isComplete=is_complete,
)
conflict = sql.on_duplicate_key_update(
distance=distance,
is_lock=is_lock,
is_clear=is_clear,
is_complete=is_complete,
isLock=is_lock,
isClear=is_clear,
isComplete=is_complete,
)
result = self.execute(conflict)
@ -324,7 +326,7 @@ class Mai2ItemData(BaseData):
return result.fetchall()
def get_map(self, user_id: int, map_id: int) -> Optional[Row]:
sql = map.select(and_(map.c.user == user_id, map.c.map_id == map_id))
sql = map.select(and_(map.c.user == user_id, map.c.mapId == map_id))
result = self.execute(sql)
if result is None:
@ -341,16 +343,16 @@ class Mai2ItemData(BaseData):
) -> None:
sql = insert(character).values(
user=user_id,
character_id=character_id,
characterId=character_id,
level=level,
awakening=awakening,
use_count=use_count,
useCount=use_count,
)
conflict = sql.on_duplicate_key_update(
level=level,
awakening=awakening,
use_count=use_count,
useCount=use_count,
)
result = self.execute(conflict)
@ -385,7 +387,25 @@ class Mai2ItemData(BaseData):
result = self.execute(sql)
if result is None:
return None
return result.fetchone()
return result.fetchall()
def put_friend_season_ranking(
self, aime_id: int, friend_season_ranking_data: Dict
) -> Optional[int]:
sql = insert(friend_season_ranking).values(
user=aime_id, **friend_season_ranking_data
)
conflict = sql.on_duplicate_key_update(**friend_season_ranking_data)
result = self.execute(conflict)
if result is None:
self.logger.warn(
f"put_friend_season_ranking: failed to insert",
f"friend_season_ranking! aime_id: {aime_id}"
)
return None
return result.lastrowid
def put_favorite(
self, user_id: int, kind: int, item_id_list: List[int]

View File

@ -158,6 +158,7 @@ extend = Table(
Column("sortMusicSetting", Integer),
Column("selectedCardList", JSON),
Column("encountMapNpcList", JSON),
Column("playStatusSetting", Integer, server_default="0"),
UniqueConstraint("user", "version", name="mai2_profile_extend_uk"),
mysql_charset="utf8mb4",
)
@ -178,6 +179,7 @@ option = Table(
Column("slideSpeed", Integer),
Column("touchSpeed", Integer),
Column("tapDesign", Integer),
Column("tapSe", Integer, server_default="0"),
Column("holdDesign", Integer),
Column("slideDesign", Integer),
Column("starType", Integer),
@ -298,8 +300,8 @@ class Mai2ProfileData(BaseData):
def get_profile_detail(self, user_id: int, version: int) -> Optional[Row]:
sql = select(detail).where(
and_(detail.c.user == user_id, detail.c.version == version)
)
and_(detail.c.user == user_id, detail.c.version <= version)
).order_by(detail.c.version.desc())
result = self.execute(sql)
if result is None:
@ -323,8 +325,8 @@ class Mai2ProfileData(BaseData):
def get_profile_ghost(self, user_id: int, version: int) -> Optional[Row]:
sql = select(ghost).where(
and_(ghost.c.user == user_id, ghost.c.version_int == version)
)
and_(ghost.c.user == user_id, ghost.c.version_int <= version)
).order_by(ghost.c.version.desc())
result = self.execute(sql)
if result is None:
@ -348,8 +350,8 @@ class Mai2ProfileData(BaseData):
def get_profile_extend(self, user_id: int, version: int) -> Optional[Row]:
sql = select(extend).where(
and_(extend.c.user == user_id, extend.c.version == version)
)
and_(extend.c.user == user_id, extend.c.version <= version)
).order_by(extend.c.version.desc())
result = self.execute(sql)
if result is None:
@ -373,8 +375,8 @@ class Mai2ProfileData(BaseData):
def get_profile_option(self, user_id: int, version: int) -> Optional[Row]:
sql = select(option).where(
and_(option.c.user == user_id, option.c.version == version)
)
and_(option.c.user == user_id, option.c.version <= version)
).order_by(option.c.version.desc())
result = self.execute(sql)
if result is None:
@ -398,8 +400,8 @@ class Mai2ProfileData(BaseData):
def get_profile_rating(self, user_id: int, version: int) -> Optional[Row]:
sql = select(rating).where(
and_(rating.c.user == user_id, rating.c.version == version)
)
and_(rating.c.user == user_id, rating.c.version <= version)
).order_by(rating.c.version.desc())
result = self.execute(sql)
if result is None:

View File

@ -25,6 +25,7 @@ best_score = Table(
Column("syncStatus", Integer),
Column("deluxscoreMax", Integer),
Column("scoreRank", Integer),
Column("extNum1", Integer, server_default="0"),
UniqueConstraint("user", "musicId", "level", name="mai2_score_best_uk"),
mysql_charset="utf8mb4",
)
@ -143,6 +144,7 @@ playlog = Table(
Column("isNewFree", Boolean),
Column("extNum1", Integer),
Column("extNum2", Integer),
Column("extNum4", Integer, server_default="0"),
Column("trialPlayAchievement", Integer),
mysql_charset="utf8mb4",
)

View File

@ -16,6 +16,7 @@ event = Table(
Column("eventId", Integer),
Column("type", Integer),
Column("name", String(255)),
Column("startDate", TIMESTAMP, server_default=func.now()),
Column("enabled", Boolean, server_default="1"),
UniqueConstraint("version", "eventId", "type", name="mai2_static_event_uk"),
mysql_charset="utf8mb4",
@ -108,17 +109,17 @@ class Mai2StaticData(BaseData):
return None
return result.fetchall()
def toggle_game_events(
def toggle_game_event(
self, version: int, event_id: int, toggle: bool
) -> Optional[List]:
sql = event.update(
and_(event.c.version == version, event.c.event_id == event_id)
and_(event.c.version == version, event.c.eventId == event_id)
).values(enabled=int(toggle))
result = self.execute(sql)
if result is None:
self.logger.warning(
f"toggle_game_events: Failed to update event! event_id {event_id} toggle {toggle}"
f"toggle_game_event: Failed to update event! event_id {event_id} toggle {toggle}"
)
return result.last_updated_params()

View File

@ -1,7 +1,4 @@
from typing import Any, List, Dict
from datetime import datetime, timedelta
import pytz
import json
from typing import Dict
from core.config import CoreConfig
from titles.mai2.universe import Mai2Universe