mai2: pre-dx music reading

This commit is contained in:
Hay1tsme 2026-02-13 21:37:08 -05:00
parent d75f62bcb4
commit 2d2b24739d

View File

@ -53,6 +53,10 @@ class Mai2Reader(BaseReader):
self.logger.error(f"tables directory not found in {self.bin_dir}")
return
if not os.path.exists(f"{self.opt_dir}/tables"):
self.logger.warning(f"tables directory not found in {self.opt_dir}, not using")
self.opt_dir = None
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")
@ -63,41 +67,54 @@ class Mai2Reader(BaseReader):
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)
evt_table = self.load_table_raw(f"{self.bin_dir}/tables", "mmEvent.bin", key, f"{self.opt_dir}/tables" if self.opt_dir else None)
jp_table = self.load_table_raw(f"{self.bin_dir}/tables", "mmtextout_jp.bin", key, f"{self.opt_dir}/tables" if self.opt_dir else None)
#en_table = self.load_table_raw(f"{self.bin_dir}/tables", "mmtextout_ex.bin", key, f"{self.opt_dir}/tables" if self.opt_dir else None)
en_table = []
score_table = self.load_table_raw(f"{self.bin_dir}/tables", "mmScore.bin", key, f"{self.opt_dir}/tables" if self.opt_dir else None)
music_table = self.load_table_raw(f"{self.bin_dir}/tables", "mmMusic.bin", key, f"{self.opt_dir}/tables" if self.opt_dir else None)
genre_table = self.load_table_raw(f"{self.bin_dir}/tables", "mmGenre.bin", key, f"{self.opt_dir}/tables" if self.opt_dir else None)
txt_table = {} # Build our translation table
for entry in jp_table:
vals = list(entry.values())
txt_table[vals[0]] = ", ".join(vals[1:])
for entry in en_table:
vals = list(entry.values())
txt_table[vals[0]] = ", ".join(vals[1:])
genre_lookup = {}
for entry in genre_table:
genre_lookup[entry['ID']] = txt_table[entry['名前テキスト']]
self.logger.info(f"Insert {len(evt_table)} events")
await self.read_old_events(evt_table)
await 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)
await self.read_old_events(evt_table)
await self.read_old_music(score_table, txt_table)
return
self.logger.info(f"Insert {len(score_table)} charts")
await self.read_old_music(music_table, score_table, txt_table, genre_lookup)
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}"):
def load_table_raw(self, dir: str, file: str, key: Optional[bytes], opt_dir: Optional[str] = None) -> Optional[List[Dict[str, str]]]:
if opt_dir is not None and os.path.exists(f"{opt_dir}/{file}"):
fpath = f"{opt_dir}/{file}"
else:
fpath = f"{dir}/{file}"
if not os.path.exists(fpath):
self.logger.warning(f"file {file} does not exist in directory {dir}, skipping")
return
self.logger.info(f"Load table {file} from {dir}")
self.logger.info(f"Load table {fpath}")
if key is not None:
cipher = AES.new(key, AES.MODE_CBC)
with open(f"{dir}/{file}", "rb") as f:
with open(fpath, "rb") as f:
f_encrypted = f.read()
f_data = cipher.decrypt(f_encrypted)[0x10:]
else:
with open(f"{dir}/{file}", "rb") as f:
with open(fpath, "rb") as f:
f_data = f.read()[0x10:]
if f_data is None or not f_data:
self.logger.warning(f"file {dir} could not be read, skipping")
self.logger.warning(f"file {fpath} 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
@ -325,20 +342,37 @@ class Mai2Reader(BaseReader):
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}')
await self.data.static.put_game_event(self.version, 0, evt_id, name)
if not (is_exp or is_aou):
await self.data.static.toggle_game_event(self.version, evt_id, False)
async 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:
async def read_old_music(self, music: Optional[List[Dict[str, str]]], scores: Optional[List[Dict[str, str]]], text: Optional[Dict[str, str]], genre: Dict[str, str]) -> None:
if music is None or scores is None or text is None:
return
# TODO
last_music = music[0]
for score in scores:
mid = score['ID'][:-2]
cid = score['ID'][-2:]
if last_music['ID'] != mid:
for x in range(len(music)):
if music[x]['ID'] == mid:
last_music = music[x]
break
await self.data.static.put_game_music(
self.version,
int(mid),
int(cid),
text[last_music['タイトル']],
text[last_music['アーティスト']],
genre[last_music['GenreID']],
last_music['BPM'],
last_music['Ver'],
float(score['LV']),
text[f"RST_SCORECREATOR_{int(score['譜面作者ID']):04d}"]
)
async def read_opt_info(self, directory: str) -> Optional[int]:
datacfg_file = os.path.join(directory, "DataConfig.xml")