From 8971f9d34628071a5186be27290501e0f9fc9aae Mon Sep 17 00:00:00 2001 From: Darren Thompson Date: Sat, 2 Aug 2025 16:27:57 -0400 Subject: [PATCH] Combine module into single file to follow project pattern --- bemani/backend/ddr/{ddrsn2 => }/ddrsn2.py | 355 +++++++++++++++++- bemani/backend/ddr/ddrsn2/__init__.py | 3 - bemani/backend/ddr/ddrsn2/battlerecord.py | 26 -- bemani/backend/ddr/ddrsn2/eventinfo.py | 52 --- .../ddr/ddrsn2/globalrankingstateentry.py | 16 - bemani/backend/ddr/ddrsn2/playerinfo.py | 91 ----- bemani/backend/ddr/ddrsn2/scoreinfo.py | 40 -- bemani/backend/ddr/ddrsn2/scorerecord.py | 115 ------ bemani/backend/ddr/ddrsn2/unlockentry.py | 21 -- bemani/backend/ddr/ddrsn2/zukinteams.py | 7 - 10 files changed, 349 insertions(+), 377 deletions(-) rename bemani/backend/ddr/{ddrsn2 => }/ddrsn2.py (64%) delete mode 100644 bemani/backend/ddr/ddrsn2/__init__.py delete mode 100644 bemani/backend/ddr/ddrsn2/battlerecord.py delete mode 100644 bemani/backend/ddr/ddrsn2/eventinfo.py delete mode 100644 bemani/backend/ddr/ddrsn2/globalrankingstateentry.py delete mode 100644 bemani/backend/ddr/ddrsn2/playerinfo.py delete mode 100644 bemani/backend/ddr/ddrsn2/scoreinfo.py delete mode 100644 bemani/backend/ddr/ddrsn2/scorerecord.py delete mode 100644 bemani/backend/ddr/ddrsn2/unlockentry.py delete mode 100644 bemani/backend/ddr/ddrsn2/zukinteams.py diff --git a/bemani/backend/ddr/ddrsn2/ddrsn2.py b/bemani/backend/ddr/ddrsn2.py similarity index 64% rename from bemani/backend/ddr/ddrsn2/ddrsn2.py rename to bemani/backend/ddr/ddrsn2.py index c82dc67..bbef219 100644 --- a/bemani/backend/ddr/ddrsn2/ddrsn2.py +++ b/bemani/backend/ddr/ddrsn2.py @@ -2,14 +2,15 @@ import base64 import hashlib import html -from typing import Dict, List, Optional, Tuple +import logging +import math +from enum import IntEnum +from typing import Dict, Optional, Tuple from typing_extensions import Final +from ctypes import * from bemani.backend.ddr.base import DDRBase -from bemani.backend.ddr.ddrsn2.eventinfo import EventInfo -from bemani.backend.ddr.ddrsn2.playerinfo import PlayerInfo -from bemani.backend.ddr.ddrsn2.scoreinfo import ScoreInfo from bemani.backend.ddr.stubs import DDRSuperNova from bemani.backend.ddr.common import ( DDRGameFriendHandler, @@ -27,10 +28,352 @@ from bemani.backend.ddr.common import ( DDRGameShopHandler, DDRGameTraceHandler, ) -from bemani.common import Time, VersionConstants, Profile, intish +from bemani.common import VersionConstants, Profile, PlayStatistics, DBConstants, intish from bemani.data import Score, UserID from bemani.protocol import Node -from bemani.backend.base import Status + + +GAME_RANK_AAA = 0 +GAME_RANK_AA = 4 +GAME_RANK_A = 8 +GAME_RANK_B = 13 +GAME_RANK_C = 16 +GAME_RANK_D = 20 +GAME_RANK_E = 24 +GAME_RANK_NONE = 0xFF + + +class ScoreRankRecordStruct(Structure): + _pack_ = 1 + _fields_ = [ + ("lo_score", c_int8), + ("score", c_int16), + ("rank", c_int8, 5), + ("unk1", c_int8, 1), + ("yfc", c_int8, 1), + ("ofc", c_int8, 1), + ] + + +class ScoreRecordStruct(Structure): + _pack_ = 1 + _fields_ = [ + ("beginner", ScoreRankRecordStruct), + ("basic", ScoreRankRecordStruct), + ("difficult", ScoreRankRecordStruct), + ("expert", ScoreRankRecordStruct), + ("challenge", ScoreRankRecordStruct), + ("double_basic", ScoreRankRecordStruct), + ("double_difficult", ScoreRankRecordStruct), + ("double_expert", ScoreRankRecordStruct), + ("double_challenge", ScoreRankRecordStruct), + ] + + +class ScoreRecord: + @staticmethod + def blank() -> ScoreRecordStruct: + record = ScoreRecordStruct() + + record.beginner.rank = GAME_RANK_NONE + record.basic.rank = GAME_RANK_NONE + record.difficult.rank = GAME_RANK_NONE + record.expert.rank = GAME_RANK_NONE + record.challenge.rank = GAME_RANK_NONE + + record.double_basic.rank = GAME_RANK_NONE + record.double_difficult.rank = GAME_RANK_NONE + record.double_expert.rank = GAME_RANK_NONE + record.double_challenge.rank = GAME_RANK_NONE + + return record + + @staticmethod + def create(scores: Optional[dict[int, Score]]) -> ScoreRecordStruct: + record = ScoreRecord.blank() + + if scores is not None: + for chart in scores: + if chart == 0: + chart_rec = record.beginner + elif chart == 1: + chart_rec = record.basic + elif chart == 2: + chart_rec = record.difficult + elif chart == 3: + chart_rec = record.expert + else: + chart_rec = record.challenge + + score = scores[chart] + + formatted_score = math.floor(score.points / 10) + hi_score = formatted_score >> 8 + + lo_score = formatted_score ^ (hi_score << 8) + + chart_rec.lo_score = lo_score + chart_rec.score = hi_score + + rank = score.data.get_int("rank") + if rank == DBConstants.DDR_RANK_AAA: + chart_rec.rank = GAME_RANK_AAA + elif rank == DBConstants.DDR_RANK_AA: + chart_rec.rank = GAME_RANK_AA + elif rank == DBConstants.DDR_RANK_A: + chart_rec.rank = GAME_RANK_A + elif rank == DBConstants.DDR_RANK_B: + chart_rec.rank = GAME_RANK_B + elif rank == DBConstants.DDR_RANK_C: + chart_rec.rank = GAME_RANK_C + elif rank == DBConstants.DDR_RANK_D: + chart_rec.rank = GAME_RANK_D + else: + chart_rec.rank = GAME_RANK_E + + halo = score.data.get_int("halo") + if halo == DBConstants.DDR_HALO_PERFECT_FULL_COMBO: + chart_rec.yfc = 1 + chart_rec.ofc = 1 + elif halo == DBConstants.DDR_HALO_GREAT_FULL_COMBO: + chart_rec.yfc = 1 + + return record + + +class BattleRecordStruct(Structure): + _pack_ = 1 + _fields_ = [ + ("name", c_char * 14), + ("wins", c_uint16), + ("loses", c_uint16), + ("draws", c_uint16), + ] + + +class BattleRecord: + @staticmethod + def create(name: str, wins: int, losses: int, draws: int) -> BattleRecordStruct: + this_name = name + if len(this_name) > 8: + this_name = this_name[:8] + logging.warning("name {} too long, truncating to {}", name, this_name) + elif len(this_name) < 8: + logging.warning("name too short, padding with spaces") + this_name = this_name.ljust(8, " ") + + return BattleRecordStruct(this_name.encode("ascii"), wins, losses, draws) + + +class PlayerInfoStruct(Structure): + _pack_ = 1 + _fields_ = [ + ("count", c_uint16), # 0x00 + ("area", c_uint16), # 0x02 + ("title", c_uint16), # 0x04 + ("flag", c_uint16), # 0x06 + ("id", c_uint32), # 0x08 + ("exp", c_uint32), # 0x0c + ("weight", c_uint16), # 0x10 + ("last_cate", c_uint8), # 0x12 + ("last_mode", c_uint8), # 0x13 + ("last_type", c_uint8), # 0x14 + ("last_sort", c_uint8), # 0x15 + ("last_music", c_uint8), # 0x16 + ("_unused1", c_char * 6), # 0x17 - 0x1b + ("team", c_uint8), # 0x1c + ("_unused2", c_char * 8), # 0x1d - 0x24 + ("takeover", c_uint8), # 0x25 + ("count_b", c_uint8), # 0x26 + ("_unused3", c_char * 2), # 0x27 - 0x29 + ("groove_radar", c_uint16 * 5), # 0x2a + ("options", c_uint8 * 32), # 0x34 + ("name", c_char * 14), # 0x54 + ("_unused4", c_char * 2), # 0x62 - 0x63 + # Bit is set to 1 = already displayed, 0 = not yet displayed + ("unlock_prompt_bits", c_uint8 * 12), # 0x64 - 0x70 + ("_unused5", c_char * 20), # 0x70 - 0x83 + ("course", c_uint32 * 3), # 0x84 + ("_unused6", c_char * (0x1344 - 0x90)), + ("battle_records", BattleRecordStruct * 5), # 0x1344 + ] + + +class PlayerInfo: + @staticmethod + def create(play_stats: PlayStatistics, profile: Profile, machine_region: int) -> PlayerInfoStruct: + player = PlayerInfoStruct() + + player.count = play_stats.get_int("single_plays") + player.area = profile.get_int("area", machine_region) + player.title = profile.get_int("title") + player.flag = profile.get_int("flag") + player.id = profile.extid + player.exp = play_stats.get_int("exp") + player.weight = profile.get_int("weight") + + lastdict = profile.get_dict("last") + player.last_cate = lastdict.get_int("cate") + player.last_mode = lastdict.get_int("mode") + player.last_type = lastdict.get_int("type") + player.last_sort = lastdict.get_int("sort") + player.last_music = lastdict.get_int("music") + + player.team = profile.get_int("team") + + player.takeover = profile.get_int("takeover") + player.count_b = play_stats.get_int("battle_plays") + + idx = 0 + for entry in profile.get_int_array("gr_s", 5): + player.groove_radar[idx] = entry + idx += 1 + + idx = 0 + + if "opt" not in profile: + profile.replace_int_array("opt", 16, [2, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 2, 0, 0, 0, 0]) + + for entry in profile.get_int_array("opt", 16): + player.options[idx] = entry + idx += 1 + + player.name = (profile.get_str("name").ljust(8, " ")[:8]).encode("ascii") + + # Default, unlock everything? + for i in range(len(player.unlock_prompt_bits)): + player.unlock_prompt_bits[i] = 0xFF + + idx = 0 + for entry in profile.get_int_array("course", 3): + player.course[idx] = entry + idx += 1 + + return player + + +class ScoreInfoStruct(Structure): + _pack_ = 1 + _fields_ = [ + ("records", ScoreRecordStruct * 200), + ] + + +class ScoreInfo: + @staticmethod + def create(scores: list[Score], part: int) -> ScoreInfoStruct: + info = ScoreInfoStruct() + + scores_by_song_id: dict[int, dict[int, Score]] = {} + + for score in scores: + if score.id not in scores_by_song_id: + scores_by_song_id[score.id] = {} + + scores_by_song_id[score.id][score.chart] = score + + for i in range(len(info.records)): + if part == 2: + song_id = i + 200 + else: + song_id = i + + if song_id in scores_by_song_id: + score_record = ScoreRecord.create(scores_by_song_id[song_id]) + else: + score_record = ScoreRecord.create(None) + + info.records[i] = score_record + + return info + + +class GlobalRankingStateEntryStruct(Structure): + _pack_ = 1 + _fields_ = [ + ("id", c_int8), + ("state", c_int8), # 0 = disabled, 1 = English ("Global Ranking"), 2 = Japanese ("Gachinko Dance Matsuri") + ("_unused", c_int8 * 2), + ] + + +class GlobalRankingStateEntry: + @staticmethod + def create(id: int, state: int) -> GlobalRankingStateEntryStruct: + return GlobalRankingStateEntryStruct(id, state) + + +class ZukinTeams(IntEnum): + GREEN = 1 + RED = 2 + YELLOW = 3 + + +class UnlockEntryStruct(Structure): + _pack_ = 1 + _fields_ = [ + ("id", c_uint8), + ("enabled", c_uint8), + # Set to 1 to enable. This is only read if the other flags check out. Set all other flags to 0 to force unlock. + ("flag1", c_uint8), # Must be less than some value passed to check function (always 0x3d6???) + ("flag2", c_uint8), # Some kind of "unlock type" flag? If 0 then other flags aren't checked. + ("flag3", c_uint8), # Unused?? + ("flag4", c_uint8), + # If 0, flag2 (if flag2 is non-0) must match the type of unlock looked for by the checker function. If non-0, flag4 * 10 must be <= param4 of checker function (when is this used?) + ] + + +class UnlockEntry: + @staticmethod + def create(id: int, enabled: int, flag1: int, flag2: int, flag3: int, flag4: int) -> UnlockEntryStruct: + return UnlockEntryStruct(id, enabled, flag1, flag2, flag3, flag4) + + +class EventInfoStruct(Structure): + _pack_ = 1 + _fields_ = [ + ("unlocks", UnlockEntryStruct * 256), + ("global_ranking_state", GlobalRankingStateEntryStruct * 8), + ("_unused", c_uint8 * 4), # Unused? + ("event_ver", c_uint8), # 0x624 + ("event_end", c_uint8), # 0x625 + ("event_sel_team", c_uint8), # 0x626 + ("event_rankings", c_uint8 * 3), # 0x627 + ("event_avg", c_uint16), # 0x62a + ("event_score_green", c_uint32), # 0x62c + ("event_score_red", c_uint32), # 0x62c + ("event_score_yellow", c_uint32), # 0x62c + ("event_border", c_uint16), # 0x638 + ] + + +class EventInfo: + @staticmethod + def create() -> EventInfoStruct: + p = EventInfoStruct() + p.event_ver = 5 # Event episode + p.event_end = 1 # Event has ended + p.event_sel_team = ZukinTeams.RED # Winning team + p.event_rankings[0] = ZukinTeams.RED + p.event_rankings[1] = ZukinTeams.GREEN + p.event_rankings[2] = ZukinTeams.YELLOW + p.event_avg = 123 # ? + p.event_score_green = 57312 + p.event_score_red = 3516541 + p.event_score_yellow = 5631 + p.event_border = 123 + + for i in range(len(p.unlocks)): + p.unlocks[i].id = i & 0xFF + p.unlocks[i].enabled = 1 + + for i in range(0, len(p.global_ranking_state)): + p.global_ranking_state[i].id = i & 0xFF + p.global_ranking_state[i].state = 2 + + p.unlocks[0].enabled = 7 # Phase unlock max + + return p class DDRSuperNova2( diff --git a/bemani/backend/ddr/ddrsn2/__init__.py b/bemani/backend/ddr/ddrsn2/__init__.py deleted file mode 100644 index bba343d..0000000 --- a/bemani/backend/ddr/ddrsn2/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from bemani.backend.ddr.ddrsn2.ddrsn2 import DDRSuperNova2 - -__all__ = ["DDRSuperNova2"] diff --git a/bemani/backend/ddr/ddrsn2/battlerecord.py b/bemani/backend/ddr/ddrsn2/battlerecord.py deleted file mode 100644 index b8bd433..0000000 --- a/bemani/backend/ddr/ddrsn2/battlerecord.py +++ /dev/null @@ -1,26 +0,0 @@ -import logging -from ctypes import * - - -class BattleRecordStruct(Structure): - _pack_ = 1 - _fields_ = [ - ("name", c_char * 14), - ("wins", c_uint16), - ("loses", c_uint16), - ("draws", c_uint16), - ] - - -class BattleRecord: - @staticmethod - def create(name: str, wins: int, losses: int, draws: int) -> BattleRecordStruct: - this_name = name - if len(this_name) > 8: - this_name = this_name[:8] - logging.warning("name {} too long, truncating to {}", name, this_name) - elif len(this_name) < 8: - logging.warning("name too short, padding with spaces") - this_name = this_name.ljust(8, " ") - - return BattleRecordStruct(this_name.encode("ascii"), wins, losses, draws) diff --git a/bemani/backend/ddr/ddrsn2/eventinfo.py b/bemani/backend/ddr/ddrsn2/eventinfo.py deleted file mode 100644 index f073467..0000000 --- a/bemani/backend/ddr/ddrsn2/eventinfo.py +++ /dev/null @@ -1,52 +0,0 @@ -from ctypes import * - -from bemani.backend.ddr.ddrsn2.globalrankingstateentry import GlobalRankingStateEntryStruct -from bemani.backend.ddr.ddrsn2.unlockentry import UnlockEntryStruct -from bemani.backend.ddr.ddrsn2.zukinteams import ZukinTeams - - -class EventInfoStruct(Structure): - _pack_ = 1 - _fields_ = [ - ("unlocks", UnlockEntryStruct * 256), - ("global_ranking_state", GlobalRankingStateEntryStruct * 8), - ("_unused", c_uint8 * 4), # Unused? - ("event_ver", c_uint8), # 0x624 - ("event_end", c_uint8), # 0x625 - ("event_sel_team", c_uint8), # 0x626 - ("event_rankings", c_uint8 * 3), # 0x627 - ("event_avg", c_uint16), # 0x62a - ("event_score_green", c_uint32), # 0x62c - ("event_score_red", c_uint32), # 0x62c - ("event_score_yellow", c_uint32), # 0x62c - ("event_border", c_uint16), # 0x638 - ] - - -class EventInfo: - @staticmethod - def create() -> EventInfoStruct: - p = EventInfoStruct() - p.event_ver = 5 # Event episode - p.event_end = 1 # Event has ended - p.event_sel_team = ZukinTeams.RED # Winning team - p.event_rankings[0] = ZukinTeams.RED - p.event_rankings[1] = ZukinTeams.GREEN - p.event_rankings[2] = ZukinTeams.YELLOW - p.event_avg = 123 # ? - p.event_score_green = 57312 - p.event_score_red = 3516541 - p.event_score_yellow = 5631 - p.event_border = 123 - - for i in range(len(p.unlocks)): - p.unlocks[i].id = i & 0xFF - p.unlocks[i].enabled = 1 - - for i in range(0, len(p.global_ranking_state)): - p.global_ranking_state[i].id = i & 0xFF - p.global_ranking_state[i].state = 2 - - p.unlocks[0].enabled = 7 # Phase unlock max - - return p diff --git a/bemani/backend/ddr/ddrsn2/globalrankingstateentry.py b/bemani/backend/ddr/ddrsn2/globalrankingstateentry.py deleted file mode 100644 index 4d4e0e2..0000000 --- a/bemani/backend/ddr/ddrsn2/globalrankingstateentry.py +++ /dev/null @@ -1,16 +0,0 @@ -from ctypes import * - - -class GlobalRankingStateEntryStruct(Structure): - _pack_ = 1 - _fields_ = [ - ("id", c_int8), - ("state", c_int8), # 0 = disabled, 1 = English ("Global Ranking"), 2 = Japanese ("Gachinko Dance Matsuri") - ("_unused", c_int8 * 2), - ] - - -class GlobalRankingStateEntry: - @staticmethod - def create(id: int, state: int) -> GlobalRankingStateEntryStruct: - return GlobalRankingStateEntryStruct(id, state) diff --git a/bemani/backend/ddr/ddrsn2/playerinfo.py b/bemani/backend/ddr/ddrsn2/playerinfo.py deleted file mode 100644 index 60fc7e8..0000000 --- a/bemani/backend/ddr/ddrsn2/playerinfo.py +++ /dev/null @@ -1,91 +0,0 @@ -from ctypes import * - -from bemani.backend.ddr.ddrsn2.battlerecord import BattleRecordStruct -from bemani.common import Profile, PlayStatistics - - -class PlayerInfoStruct(Structure): - _pack_ = 1 - _fields_ = [ - ("count", c_uint16), # 0x00 - ("area", c_uint16), # 0x02 - ("title", c_uint16), # 0x04 - ("flag", c_uint16), # 0x06 - ("id", c_uint32), # 0x08 - ("exp", c_uint32), # 0x0c - ("weight", c_uint16), # 0x10 - ("last_cate", c_uint8), # 0x12 - ("last_mode", c_uint8), # 0x13 - ("last_type", c_uint8), # 0x14 - ("last_sort", c_uint8), # 0x15 - ("last_music", c_uint8), # 0x16 - ("_unused1", c_char * 6), # 0x17 - 0x1b - ("team", c_uint8), # 0x1c - ("_unused2", c_char * 8), # 0x1d - 0x24 - ("takeover", c_uint8), # 0x25 - ("count_b", c_uint8), # 0x26 - ("_unused3", c_char * 2), # 0x27 - 0x29 - ("groove_radar", c_uint16 * 5), # 0x2a - ("options", c_uint8 * 32), # 0x34 - ("name", c_char * 14), # 0x54 - ("_unused4", c_char * 2), # 0x62 - 0x63 - # Bit is set to 1 = already displayed, 0 = not yet displayed - ("unlock_prompt_bits", c_uint8 * 12), # 0x64 - 0x70 - ("_unused5", c_char * 20), # 0x70 - 0x83 - ("course", c_uint32 * 3), # 0x84 - ("_unused6", c_char * (0x1344 - 0x90)), - ("battle_records", BattleRecordStruct * 5), # 0x1344 - ] - - -class PlayerInfo: - @staticmethod - def create(play_stats: PlayStatistics, profile: Profile, machine_region: int) -> PlayerInfoStruct: - player = PlayerInfoStruct() - - player.count = play_stats.get_int("single_plays") - player.area = profile.get_int("area", machine_region) - player.title = profile.get_int("title") - player.flag = profile.get_int("flag") - player.id = profile.extid - player.exp = play_stats.get_int("exp") - player.weight = profile.get_int("weight") - - lastdict = profile.get_dict("last") - player.last_cate = lastdict.get_int("cate") - player.last_mode = lastdict.get_int("mode") - player.last_type = lastdict.get_int("type") - player.last_sort = lastdict.get_int("sort") - player.last_music = lastdict.get_int("music") - - player.team = profile.get_int("team") - - player.takeover = profile.get_int("takeover") - player.count_b = play_stats.get_int("battle_plays") - - idx = 0 - for entry in profile.get_int_array("gr_s", 5): - player.groove_radar[idx] = entry - idx += 1 - - idx = 0 - - if "opt" not in profile: - profile.replace_int_array("opt", 16, [2, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 2, 0, 0, 0, 0]) - - for entry in profile.get_int_array("opt", 16): - player.options[idx] = entry - idx += 1 - - player.name = (profile.get_str("name").ljust(8, " ")[:8]).encode("ascii") - - # Default, unlock everything? - for i in range(len(player.unlock_prompt_bits)): - player.unlock_prompt_bits[i] = 0xFF - - idx = 0 - for entry in profile.get_int_array("course", 3): - player.course[idx] = entry - idx += 1 - - return player diff --git a/bemani/backend/ddr/ddrsn2/scoreinfo.py b/bemani/backend/ddr/ddrsn2/scoreinfo.py deleted file mode 100644 index 06eb187..0000000 --- a/bemani/backend/ddr/ddrsn2/scoreinfo.py +++ /dev/null @@ -1,40 +0,0 @@ -from ctypes import * - -from bemani.backend.ddr.ddrsn2.scorerecord import ScoreRecordStruct, ScoreRecord -from bemani.data import Score - - -class ScoreInfoStruct(Structure): - _pack_ = 1 - _fields_ = [ - ("records", ScoreRecordStruct * 200), - ] - - -class ScoreInfo: - @staticmethod - def create(scores: list[Score], part: int) -> ScoreInfoStruct: - info = ScoreInfoStruct() - - scores_by_song_id: dict[int, dict[int, Score]] = {} - - for score in scores: - if score.id not in scores_by_song_id: - scores_by_song_id[score.id] = {} - - scores_by_song_id[score.id][score.chart] = score - - for i in range(len(info.records)): - if part == 2: - song_id = i + 200 - else: - song_id = i - - if song_id in scores_by_song_id: - score_record = ScoreRecord.create(scores_by_song_id[song_id]) - else: - score_record = ScoreRecord.create(None) - - info.records[i] = score_record - - return info diff --git a/bemani/backend/ddr/ddrsn2/scorerecord.py b/bemani/backend/ddr/ddrsn2/scorerecord.py deleted file mode 100644 index b784ea2..0000000 --- a/bemani/backend/ddr/ddrsn2/scorerecord.py +++ /dev/null @@ -1,115 +0,0 @@ -import math - -from ctypes import * -from typing import Optional - -from bemani.common import DBConstants -from bemani.data import Score - - -class ScoreRankRecordStruct(Structure): - _pack_ = 1 - _fields_ = [ - ("lo_score", c_int8), - ("score", c_int16), - ("rank", c_int8, 5), - ("unk1", c_int8, 1), - ("yfc", c_int8, 1), - ("ofc", c_int8, 1), - ] - - -class ScoreRecordStruct(Structure): - _pack_ = 1 - _fields_ = [ - ("beginner", ScoreRankRecordStruct), - ("basic", ScoreRankRecordStruct), - ("difficult", ScoreRankRecordStruct), - ("expert", ScoreRankRecordStruct), - ("challenge", ScoreRankRecordStruct), - ("double_basic", ScoreRankRecordStruct), - ("double_difficult", ScoreRankRecordStruct), - ("double_expert", ScoreRankRecordStruct), - ("double_challenge", ScoreRankRecordStruct), - ] - - -GAME_RANK_AAA = 0 -GAME_RANK_AA = 4 -GAME_RANK_A = 8 -GAME_RANK_B = 13 -GAME_RANK_C = 16 -GAME_RANK_D = 20 -GAME_RANK_E = 24 -GAME_RANK_NONE = 0xFF - - -class ScoreRecord: - @staticmethod - def blank() -> ScoreRecordStruct: - record = ScoreRecordStruct() - - record.beginner.rank = GAME_RANK_NONE - record.basic.rank = GAME_RANK_NONE - record.difficult.rank = GAME_RANK_NONE - record.expert.rank = GAME_RANK_NONE - record.challenge.rank = GAME_RANK_NONE - - record.double_basic.rank = GAME_RANK_NONE - record.double_difficult.rank = GAME_RANK_NONE - record.double_expert.rank = GAME_RANK_NONE - record.double_challenge.rank = GAME_RANK_NONE - - return record - - @staticmethod - def create(scores: Optional[dict[int, Score]]) -> ScoreRecordStruct: - record = ScoreRecord.blank() - - if scores is not None: - for chart in scores: - if chart == 0: - chart_rec = record.beginner - elif chart == 1: - chart_rec = record.basic - elif chart == 2: - chart_rec = record.difficult - elif chart == 3: - chart_rec = record.expert - else: - chart_rec = record.challenge - - score = scores[chart] - - formatted_score = math.floor(score.points / 10) - hi_score = formatted_score >> 8 - - lo_score = formatted_score ^ (hi_score << 8) - - chart_rec.lo_score = lo_score - chart_rec.score = hi_score - - rank = score.data.get_int("rank") - if rank == DBConstants.DDR_RANK_AAA: - chart_rec.rank = GAME_RANK_AAA - elif rank == DBConstants.DDR_RANK_AA: - chart_rec.rank = GAME_RANK_AA - elif rank == DBConstants.DDR_RANK_A: - chart_rec.rank = GAME_RANK_A - elif rank == DBConstants.DDR_RANK_B: - chart_rec.rank = GAME_RANK_B - elif rank == DBConstants.DDR_RANK_C: - chart_rec.rank = GAME_RANK_C - elif rank == DBConstants.DDR_RANK_D: - chart_rec.rank = GAME_RANK_D - else: - chart_rec.rank = GAME_RANK_E - - halo = score.data.get_int("halo") - if halo == DBConstants.DDR_HALO_PERFECT_FULL_COMBO: - chart_rec.yfc = 1 - chart_rec.ofc = 1 - elif halo == DBConstants.DDR_HALO_GREAT_FULL_COMBO: - chart_rec.yfc = 1 - - return record diff --git a/bemani/backend/ddr/ddrsn2/unlockentry.py b/bemani/backend/ddr/ddrsn2/unlockentry.py deleted file mode 100644 index 093f6ee..0000000 --- a/bemani/backend/ddr/ddrsn2/unlockentry.py +++ /dev/null @@ -1,21 +0,0 @@ -from ctypes import * - - -class UnlockEntryStruct(Structure): - _pack_ = 1 - _fields_ = [ - ("id", c_uint8), - ("enabled", c_uint8), - # Set to 1 to enable. This is only read if the other flags check out. Set all other flags to 0 to force unlock. - ("flag1", c_uint8), # Must be less than some value passed to check function (always 0x3d6???) - ("flag2", c_uint8), # Some kind of "unlock type" flag? If 0 then other flags aren't checked. - ("flag3", c_uint8), # Unused?? - ("flag4", c_uint8), - # If 0, flag2 (if flag2 is non-0) must match the type of unlock looked for by the checker function. If non-0, flag4 * 10 must be <= param4 of checker function (when is this used?) - ] - - -class UnlockEntry: - @staticmethod - def create(id: int, enabled: int, flag1: int, flag2: int, flag3: int, flag4: int) -> UnlockEntryStruct: - return UnlockEntryStruct(id, enabled, flag1, flag2, flag3, flag4) diff --git a/bemani/backend/ddr/ddrsn2/zukinteams.py b/bemani/backend/ddr/ddrsn2/zukinteams.py deleted file mode 100644 index 681bb43..0000000 --- a/bemani/backend/ddr/ddrsn2/zukinteams.py +++ /dev/null @@ -1,7 +0,0 @@ -from enum import IntEnum - - -class ZukinTeams(IntEnum): - GREEN = 1 - RED = 2 - YELLOW = 3